上一篇咱們完成了第一步 (數據劫持),從而完成了對屬性的監聽,這一篇咱們來完成最後一步(發佈-訂閱)node
發佈訂閱的關係主要靠數組來維護,訂閱就是將函數添加到數組,發佈就是將數組中的函數執行bash
// 建立一個數組
function Dep() {
this.subs = []
}
Dep.prototype = {
// 將訂閱內容添加到數組
addSub(fn) {
this.subs.push(fn)
},
// 每一個訂閱內容都有一個 update 方法,通知內容發佈
notify() {
this.subs.forEach(sub => sub.update())
}
}
function Watcher(fn) {
this.fn = fn
}
// 發佈內容
Watcher.prototype.update = function () {
this.fn()
}
let watcher = new Watcher(() => console.log('娜美'))
let dep = new Dep()
dep.addSub(watcher)
dep.addSub(watcher)
dep.notify() // 娜美 ,娜美
複製代碼
咱們的Dep構造函數不須要變更,須要先修改下 Wathermvvm
...
+ let hasWatcher = false // 添加一個開關 確保Watcher只實例化一次
if (node.nodeType === 3 && reg.test(txt)) {
function replaceTxt() {
node.textContent = txt.replace(reg, (matched, placeholder) => {
// 只有第一次編譯纔會實例化Watcher
+ if (!hasWatcher) {
+ new Watcher(vm, placeholder, replaceTxt); // 監聽變化,進行匹配替換內容
+ hasWatcher = true
+ }
return placeholder.split('.').reduce((val, key) => {
return val[key];
}, vm);
});
};
// 替換
replaceTxt();
}
// 修改 Watcher
function Watcher(vm, exp, fn) {
this.fn = fn
+ this.arr = exp.split('.') // exp 是正則匹配後的字符串 a.b.c
+ this.vm = vm // vm 是mvvm的實例對象
+ Dep.target = this // 添加一個標識,用於收集訂閱時的判斷
// 經過訪問 vm 的屬性來觸發對應屬性的 get 如 vm.a.b.c, 從而收集當前的Wathcer
+ let val = vm
+ this.arr.forEach(key => {
+ val = val[key]
+ })
// 收集完訂閱內容時置空
+ Dep.target = null
}
Watcher.prototype.update = function() {
this.fn();
}
複製代碼
上面咱們說到經過屬性的get方法來收集Watcher, 那麼咱們還須要修改下Observe函數
// + 號表示新增代碼
function Observe(data) {
+ let dep = new Dep(); // 實例化Dep ,建立一個用來存放Watcher的數組
for (let key in data) {
let val = data[key]
observe(val)
Object.defineProperty(data, key, {
configurable: true,
get() {
// 判斷 Dep.target 存在,向數組中添加 Watcher
+ Dep.target && dep.addSub(Dep.target)
return val;
},
set(newVal) {
if (val === newVal) {
return;
}
console.log('數據被修改')
val = newVal;
observe(newVal);
+ dep.notify() // 數據被修改時 觸發Watcher更新視圖
}
})
}
}
複製代碼
控制檯修改name屬性, 視圖也發生了變化post
完整DEMO地址測試