computed
在初始化 Vue 實例階段會執行 initState 處理 data、props、computed、watcher 數據屬性,其中在 initComputed 是處理 computed 的方法,它的定義在 src/core/instance/state.js 中:
首先創建 vm._computedWatchers 為一個空對象,接著對 computed 對象做遍歷,拿到計算屬性的每一個 userDef,然後嘗試獲取這個 userDef 對應的 getter 函數。
接下來為每一個 getter 創建一個 watcher,這個 watcher 和渲染 watcher 有一點很大的不同,它是一個 computed watcher,因為 const computedWatcherOptions = { lazy: true }。
最後對判斷如果 key 不是 vm 的屬性,則調用 defineComputed(vm, key, userDef)。接下來我們看一下 defineComputed 的實現:
在 defineComputed 方法中會將每個計算屬性通過 Object.defineProperty 定義到 vm 實例上,重點關注這裡為每個計算屬性定義的 getter 和 setter 方法,平時計算屬性有 setter 的情況比較少,我們重點關注一下 getter 部分, getter 對應的是 createComputedGetter(key) 的返回值,來看一下它的定義:
可以看到將 computedGetter 作為屬性的 getter 方法,當訪問這個計算屬性時,會拿到 watcher.value 的值,即拿到在上面 initComputed 中 getter 方法的執行結果。
整個計算屬性的初始化過程到此結束。
watch
偵聽屬性的初始化也是發生在 Vue 的實例初始化階段的 initState 函數中,通過 initWatch 函數去處理:
首先對 watch 對象做遍歷,拿到每一個 handler,因為 Vue 是支持 watch 的同一個 key 對應多個 handler,所以如果 handler 是一個數組,則遍歷這個數組,調用 createWatcher 方法,否則直接調用 createWatcher:
這裡調用 vm.$watch(keyOrFn, handler, options) 函數,$watch 是 Vue 原型上的方法,它是在執行 stateMixin 的時候定義的:
也就是說,偵聽屬性 watch 最終會調用 $watch 方法,這個方法首先判斷 cb 如果是一個對象,則調用 createWatcher 方法,這是因為 $watch 方法是用戶可以直接調用的,它可以傳遞一個對象,也可以傳遞函數。
接著執行 const watcher = new Watcher(vm, expOrFn, cb, options) 實例化了一個 watcher,這裡需要注意一點這是一個 user watcher,因為 options.user = true。
通過實例化 watcher 的方式,一旦我們 watch 的數據發送變化,它最終會執行 watcher 的 run 方法,執行回調函數 cb,並且如果我們設置了 immediate 為 true,則直接會執行回調函數 cb。最後返回了一個 unwatchFn 方法,它會調用 teardown 方法去移除這個 watcher。
所以本質上偵聽屬性也是基於 Watcher 實現的,它是一個 user watcher。其實 Watcher 支持了不同的類型,下面我們梳理一下它有哪些類型以及它們的作用。
Watcher options
Watcher 的構造函數對 options 做的了處理,代碼如下:
所以 watcher 總共有 4 種類型,我們來一一分析它們,看看不同的類型執行的邏輯有哪些差別。
deep watcher
通常,如果我們想對一下對象做深度觀測的時候,需要設置這個屬性為 true,考慮到這種情況:
當我們修改 b 屬性時發現沒有任何列印,因為我們是 watch 了 a 對象,只觸發了 a 的 getter,並沒有觸發 a.b 的 getter,所以並沒有訂閱它的變化,當我們執行 vm.a.b = 2 賦值的時候,雖然觸發了 setter,但沒有可通知的對象,所以也並不會觸發 watch 的回調函數了。
而我們只需要對代碼做稍稍修改,就可以觀測到這個變化了:
這樣就創建了一個 deep watcher 了,在 watcher 執行 get 求值的過程中有一段邏輯:
在對 watch 的表達式或者函數求值後,會調用 traverse 函數,它的定義在 src/core/observer/traverse.js 中:
traverse 的邏輯也很簡單,它實際上就是對一個對象做深層遞歸遍歷,因為遍歷過程中就是對一個子對象的訪問,會觸發它們的 getter 過程,這樣就可以收集到依賴,也就是訂閱它們變化的 watcher,這個函數實現還有一個小的優化,遍歷過程中會把子響應式對象通過它們的 dep id 記錄到 seenObjects,避免以後重複訪問。
那麼在執行了 traverse 後,我們再對 watch 的對象內部任何一個值做修改,也會調用 watcher 的回調函數了。
user watcher
通過 vm.$watch 創建的 watcher 是一個 user watcher,而 user watcher 主要作用是在對 watcher 求值以及在執行回調函數的時候,當出現錯誤時,會調用 handleError 處理錯誤。
computed watcher
computed watcher 就是上面專為計算屬性而使用的 watcher。
sync watcher
當響應式數據發送變化後,觸發了 watcher.update(),只是把這個 watcher 推送到一個隊列中,在 nextTick 後才會真正執行 watcher 的回調函數。而一旦我們設置了 sync,就可以直接調用 run() 來同步執行 watcher 的回調函數。
只有當我們需要 watch 的值的變化到執行 watcher 的回調函數是一個同步過程的時候才會去設置該屬性為 true。
作者:明裡人 來源:掘金 著作權歸作者所有。商業轉載請聯繫作者獲得授權,非商業轉載請註明出處。