什麼是依賴收集呢,依賴收集就是響應式對象的getter過程就是依賴收集,我們來看一下源碼中對getter的實現,在core/observer/index.js中:

首先它會判斷getter是否存在,如果存在就取obj的getter,如果不存在就取當時的值

這個值上面也有定義:

裡面if的判斷是判斷dep.target ,Dep是整個 getter 依賴收集的核心 ,Dep是一個 Class,它定義了一些屬性和方法,這裡需要特別注意的是它有一個靜態屬性target,這是一個全局唯一Watcher,同一時間只能有一個全局的Watcher被計算,Dep實際上就是對Watcher的一種管理,Dep脫離Watcher單獨存在是沒有意義的,Watcher是一個 Class,在它的構造函數中,定義了一些和Dep相關的屬性, 這個Dep的值就是:

就是一個類,它的主要目的就是建立數據和watcher一個橋樑,那麼dep.target就是一個全局的watcher,回到上面如果dep.target存在就會執行dep.depend函數,那什麼時候會執行getter函數呢?

當mountComponent方法執行時候會執行下面這個方法,這個方法作用就是render一個vnode,然後把這個vnode通過update patch到我們dom上,並且這個updateComponent是作為watcher的getter傳入的,當我們在執行updateComponent時候就會生成一個渲染watcher

這個watcher在生成時候初始化了很多變數,this.depsthis.newDeps表示Watcher實例持有的Dep實例的數組;而this.depIdsthis.newDepIds分別代表this.depsthis.newDepsid

後面判斷第二個參數expOrFn如果是一個函數那麼就把這個函數給了watcher的getter,下面有判斷因為我們這個是一個渲染watcher,所以不可以執行lazy watcher 所以執行this.get()方法,get方法的定義:

get執行時候要首先執行pushTarget方法,pushTarget這個方法的定義是在observer/dep.js方法裡面定義的:

targetStack是一個類似棧的數組,判斷如果dep.target存在就會把dep.target放在這個targetStack這個棧裡面,並且把_target賦值給dep.target ,這樣做的目的就是利用棧的數據結構保留了當前計算的target

這樣我們回到之前的get方法,當我們執行this.getter時候就是就是執行mountComponent方法中的mountComponent的函數,這樣會執行render函數裡面的render.call這樣就可以獲取到定義在模板中一些數據,所以我們就可以訪問到一些數據的getter等數據,vm._render()過程中,會觸發所有數據的 getter,這樣實際上已經完成了一個依賴收集的過程

然後回到最初的方法每個對象值的 getter 都持有一個dep,在觸發 getter 的時候會調用dep.depend()方法,也就會執行Dep.target.addDep(this)

我們會執行dep.depend方法,Dep.target已經被賦值為渲染watcher,那麼就執行到addDep方法,那麼這個depend方法就是:

這裡會定義addDep函數,做一些邏輯判斷(保證同一數據不會被添加多次)後執行dep.addSub(this),那麼就會執行this.subs.push(sub),也就是說把當前的watcher訂閱到這個數據持有的dep的subs中,這個目的是為後續數據變化時候能通知到哪些subs做準備

如果newdepids以及depids沒有id,就會執行dep.addsub,將watcher放到這個this.subs。

回到get函數,get函數中最後會執行

這樣就會執行popTarget會恢復上一次執行的target,這樣就會執行cleanupDeps,Dep.target恢復成上一個狀態,因為當前 vm 的數據依賴收集已經完成,那麼對應的渲染Dep.target也需要改變,這個方法的作用就是清除依賴。依賴收集就是收集它的watcher,作為一個訂閱者。

清空依賴收集的過程:

考慮到 Vue 是數據驅動的,所以每次數據變化都會重新 render,那麼 vm._render() 方法又會再次執行,並再次觸發數據的 getters,所以 Wathcer 在構造函數中會初始化 2 個 Dep 實例數組,newDeps 表示新添加的 Dep 實例數組,而 deps 表示上一次添加的 Dep 實例數組。

在執行 cleanupDeps 函數的時候,會首先遍歷 deps,移除對 dep 的訂閱,然後把 newDepIds 和 depIds 交換,newDeps 和 deps 交換,並把 newDepIds 和 newDeps 清空。

那麼為什麼需要做 deps 訂閱的移除呢,在添加 deps 的訂閱過程,已經能通過 id 去重避免重複訂閱了。Vue 設計了在每次添加完新的訂閱,會移除掉舊的訂閱,這樣就保證了在我們剛才的場景中,如果渲染 b 模板的時候去修改 a 模板的數據,a 數據訂閱回調已經被移除了,所以不會有任何浪費。


推薦閱讀:
相关文章