vue源碼系列05_發佈訂閱模式

vue源碼系列05_發佈訂閱模式


所謂依賴收集,就是在每個數據渲染更新的時候,給每個數據添加一個watcher監聽類,當該數據發生變化時,用一個dep隊列來實現收集這些watcher,最後逐一觸發watcher中的update進行渲染
這是網友的一張圖

在這裏插入圖片描述
概要:

  1. 在數據初始化的時候,會給每個屬性配置一個dep類監視並綁定給同一個watcher (該watcher是全局watcher)
  2. 當第一次頁面渲染的時候,全局watcher會默認執行update方法進行頁面更新
  3. dep內部有一個棧專門保存watcher,當dep負責的那個屬性數據發生改變時,會依次觸發棧內所有watcher的update方法進行頁面渲染。(發佈)
  4. 當然watcher(視圖)可以包含多個dep(訂閱),當它所負責dep中有一個數據發生改變了,那個dep會觸發 notify() 方法使得該 watcher 觸發 update() 進行頁面渲染

可以簡單地理解爲:

  • 一個視圖(watcher)可以包含多個dep(數據),當它所包含的dep(數據)發生改變時(數據劫持),該視圖就會自動觸發更新,而其他視圖(watcher)如果沒有包含發生數據變化的那個dep,就不會發生視圖更新從而實現局部更新
  • 一個數據(dep)也可以同時運用在多個視圖中(watcher),當該dep所監視的數據發生改變時,它所保存的視圖(watcher)將會按順序發生視圖更新,並且不影響其他watcher(發佈)
    以下爲依賴收集的流程

dep.js

主要的操作:

  1. 給不同的dep賦一個id
  2. 創建sub數組(實際上是隊列)
  3. 創建stack棧,創建保存watcher與取出watcher的方法,並且讓 Dep.target 得到當前 watcher (後面有用到)

主要的幾個方法

  • addSub() 訂閱
    向 sub 數組添加一個watcher
  • notify() 發佈
    遍歷 sub 數組,讓每個watcher執行 update() 方法
  • depend() 存watcher
    調用watcher.addDep(this),讓watcher記錄Dep
let id = 0
class Dep {
    constructor(){
        this.id = id++
        this.subs = []
    }
    addSub(watcher){ //訂閱
        this.subs.push(watcher)
    }
    notify(){ //發佈
        this.subs.forEach(watcher =>{
            watcher.update()
        })
    }
    depend(){
        if (Dep.target){
            Dep.target.addDep(this)
        }
    }
}
// 保存當前watcher
let stack = []
export function pushTarget(watcher) {
    Dep.target = watcher
    stack.push(watcher)
}
export function popTarget() {
    stack.pop()
    Dep.target = stack[stack.length - 1]
}

export default Dep

watcher.js

  1. 在 get() 方法中,在更新方法之前壓入棧,更新之後退出棧
get() {
    pushTarget(this)
    let value = this.getter()
    popTarget()
    return value // 返回老值
}
  1. 添加update更新方法
  2. 給每個watcher添加 dep數組與depId的Set集合
  3. 添加 addDep() 方法 去重
addDep(dep) {
    let id = dep.id
    // 當該watcher沒有相同的 dep
    if (!this.depsId.has(id)) {
        this.depsId.add(id)
        this.deps.push(dep) // 記錄當前dep
        dep.addSub(this) // 訂閱
    }
}
  1. 實現異步更新,這一步是爲了防止同一個watcher連續多次刷新,通過這一步我們可以實現只運行最後一步,提高性能(批量更新)
let has = {}
let queue = [];

function flusqueue() {
    queue.forEach(watcher => watcher.run())
    has = {};
    queue = []
}
function queueWatcher(watcher) {
    let id = watcher.id
    if (has[id] == null) {
        has[id] = true
        queue.push(watcher)
    }
    nextTick(flusqueue)
}

// 異步執行
let callbacks = [];

function flushCallbacks() {
    callbacks.forEach(cb => cb())
}
function nextTick(flusqueue) {
    callbacks.push(flusqueue)
    let asyncFn = () => {
        flushCallbacks()
    }
    if (Promise) {
        Promise.resolve().then(asyncFn)
    }
    setTimeout(asyncFn, 0)
}

defineReactive()

在每個數據添加響應式的時候,我們給它們添加一個dep,當屬性設置的時候,實現更新 dep.notify()

export function defineReactive(data, key, value) {
    let childOb = observe(value) // 遞歸,對data中的對象進行響應式操作,遞歸實現深度檢測
    let dep = new Dep()
    Object.defineProperty(data, key, {
        get() { // 獲取值的時候做一些操作
            if(Dep.target){
                // wacher 裏面記錄dep 也在dep裏面記錄watcher
                dep.depend()
                // dep.addSub(watcher)
                if(childOb){
                    childOb.dep.depend() // 數組收集當前渲染的watcher
                    dependArray(value) // 收集兒子的依賴
                }
            }
            return value;
        },
        set(newValue) { // 也可以做一些操作
            console.log("設置屬性",newValue)
            if (newValue == value) return;
            observe(newValue); //繼續劫持用戶設置的值,因爲用戶設置的值有可能是一個對象
            value = newValue;
            // 當屬性設置的時候,實現更新
            dep.notify()
        }
    })
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章