Vue響應式原理簡易Mvvm三步走第三步 (發佈-訂閱)

前言

上一篇咱們完成了第一步 (數據劫持),從而完成了對屬性的監聽,這一篇咱們來完成最後一步(發佈-訂閱)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

好了,如今咱們終於完成了一個簡易的Mvvm,但願對你們會有幫助。

完整DEMO地址測試

參考資料

相關文章
相關標籤/搜索