摘要:被讀取,包裝的函數觸發,第一次會進行計算被調用,進而被調用,被設置為,舊值頁面被緩存起來。計算會讀取,此時就收集到同時也會保存到的依賴收集器用于下一步。
寫文章不容易,點個贊唄兄弟
專注 Vue 源碼分享,文章分為白話版和 源碼版,白話版助于理解工作原理,源碼版助于了解內部詳情,讓我們一起學習吧
研究基于 Vue版本 【2.5.17】
如果你覺得排版難看,請點擊 下面鏈接 或者 拉到 下面關注公眾號也可以吧
【Vue原理】Computed - 源碼版
今天要記錄 computed 的源碼,有時候想,理解不就好了嗎,為什么要記錄一遍源碼。現在終于想通了
過了一段時間之后,你就會忘記你的所謂理解是怎么來的
“哎,為什么會這么做,關系為什么是這樣,我c....”
于是,記錄并簡化源碼,就有助我們迅速找到根源,解決我們的疑惑,還能加強我們的理解
好吧
嗯,這篇文章很長很詳細哦,做好閱讀的準備,唔該
我們重點說明,幾個問題的源碼實現
1、computed 的 月老身份的來源 2、computed 怎么計算 3、computed 的緩存是怎么做的 4、computed 什么時候初始化 5、computed 是怎么可以直接使用實例訪問到的
問題不會按順序解析,因為這些問題會互相關聯,在探索源碼的過程中,你自然會得到答案
首先,從這個問題開始我們今天的探索之旅,請看源碼
什么時候初始化function Vue(){ ... 其他處理 initState(this) ...解析模板,生成DOM 插入頁面 } function initState(vm) { var opts = vm.$options; if (opts.computed) { initComputed(vm, opts.computed); } ..... }
沒錯,當你調用 Vue 創建實例過程中,會去處理各種選項,其中包括處理 computed
處理 computed 的方法是 initComputed,下面就呈上 源碼
initComputedfunction initComputed(vm, computed) { var watchers = vm._computedWatchers = Object.create(null); for (var key in computed) { var userDef = computed[key]; var getter = typeof userDef === "function" ? userDef: userDef.get; // 每個 computed 都創建一個 watcher // watcher 用來存儲計算值,判斷是否需要重新計算 watchers[key] = new Watcher(vm, getter, { lazy: true }); // 判斷是否有重名的屬性 if (! (key in vm)) { defineComputed(vm, key, userDef); } } }
initComputed 這段代碼做了幾件事
1、每個 computed 配發 watcher
2、defineComputed 處理
3、收集所有 computed 的 watcher
好的,這三件事,一件一件說哈
1、每個 computed 配發 watchercomputed 到底和 watcher 有什么貓膩呢?
1、保存 computed 計算函數 2、保存計算結果 3、控制緩存計算結果是否有效
看下 Watcher 源碼構造函數
function Watcher(vm, expOrFn, options) { this.dirty = this.lazy = options.lazy; this.getter = expOrFn; this.value = this.lazy ? undefined: this.get(); };
從這段源碼中,我們再看 computed 傳了什么參數
new Watcher(vm, getter, { lazy: true })
于是,我們就知道了上面說的三個貓膩是怎么回事
1、保存設置的 getter。
把用戶設置的 computed-getter,存放到 watcher.getter 中,用于后面的計算
2、watcher.value 存放計算結果,但是這里有個條件,因為 lazy 的原因,不會新建實例并馬上讀取值
這里可以算是 Vue 的一個優化,只有你再讀取 computed,再開始計算,而不是初始化就開始計算值了
雖然沒有一開始計算,但是計算 value 還是這個 watcher.get 這個方法,來看下源碼(已省略部分代碼,下面講其他問題,會更詳細展示出來)
這個方法,其實就是執行 保存的 getter 函數,從而得到計算值,灰常簡單
Watcher.prototype.get = function() { // getter 就是 watcher 回調 var value = this.getter.call(vm, vm); return value };
3、computed 新建 watcher 的時候,傳入 lazy
沒錯,作用是把計算結果緩存起來,而不是每次使用都要重新計算
而這里呢,還把 lazy 賦值給了 dirty,為什么呢?
因為 lazy 表示一種固定描述,不可改變,表示這個 watcher 需要緩存
而 dirty 表示緩存是否可用,如果為 true,表示緩存臟了,需要重新計算,否則不用
dirty 默認是 false 的,而 lazy 賦值給 dirty,就是給一個初始值,表示 你控制緩存的任務開始了
所以記住,【dirty】 是真正的控制緩存的關鍵,而 lazy 只是起到一個開啟的作用
具體,怎么控制緩存,下面會說
2、defineComputed 處理請看源碼
function defineComputed( target, key, userDef ) { // 設置 set 為默認值,避免 computed 并沒有設置 set var set = function(){} // 如果用戶設置了set,就使用用戶的set if (userDef.set) set = userDef.set Object.defineProperty(target, key, { // 包裝get 函數,主要用于判斷計算緩存結果是否有效 get:createComputedGetter(key), set:set }); }
源碼已經被我簡短很多,但是意思是不變的
1、使用 Object.defineProperty 在 實例上computed 屬性,所以可以直接訪問
2、set 函數默認是空函數,如果用戶設置,則使用用戶設置
3、createComputedGetter 包裝返回 get 函數
重點就在第三點,為什么重要?
兩大問題都在這里得到解決,【月老牽線問題+緩存控制問題】
馬上呈上 createComputedGetter 源碼
function createComputedGetter(key) { return function() { // 獲取到相應 key 的 computed-watcher var watcher = this._computedWatchers[key]; // 如果 computed 依賴的數據變化,dirty 會變成true, 從而重新計算,然后更新緩存值 watcher.value if (watcher.dirty) { watcher.evaluate(); } // 這里是 月老computed 牽線的重點,讓雙方建立關系 if (Dep.target) { watcher.depend(); } return watcher.value } }1、緩存控制
下面這段代碼作用就是緩存控制,請往下看
if (watcher.dirty) { watcher.evaluate() }
1、watcher.evaluate 用來重新計算,更新緩存值,并重置 dirty 為false,表示緩存已更新
下面是源碼
Watcher.prototype.evaluate = function() { this.value = this.get(); // 執行完更新函數之后,立即重置標志位 this.dirty = false; };
2、只有 dirty 為 true 的時候,才會執行 evaluate
所有說通過 控制 dirty 從而控制緩存,但是怎么控制dirty 呢?
先說一個設定,computed數據A 引用了 data數據B,即A 依賴 B,所以B 會收集到 A 的 watcher
當 B 改變的時候,會通知 A 進行更新,即調用 A-watcher.update,看下源碼
Watcher.prototype.update = function() { if (this.lazy) this.dirty = true; ....還有其他無關操作,已被省略 };
當通知 computed 更新的時候,就只是 把 dirty 設置為 true,從而 讀取 comptued 時,便會調用 evalute 重新計算
2、月老牽線月老牽線的意思,在白話版中也說清楚了,這里簡單說一下
現有 頁面-P,computed- C,data- D
1、P 引用了 C,C 引用了 D 2、理論上 D 改變時, C 就會改變,C 則通知 P 更新。 3、實際上 C 讓 D 和 P 建立聯系,讓 D 改變時直接通知 P
沒錯,就是下面這段代碼搞的鬼
if (Dep.target) { watcher.depend(); }
你別看這段代碼短啊,涉及的內容真不少啊且需要點腦筋的,看源碼分分鐘繞不過來,真的服尤大怎么寫出來的
來看看 watcher.depend 的源碼
Watcher.prototype.depend = function() { var i = this.deps.length; while (i--) { // this.deps[i].depend(); dep.addSub(Dep.target) } };
這段的作用就是!(依然使用上面的例子 PCD 代號來說明)
讓 D 的依賴收集器收集到 Dep.target,而 Dep.target 當前是什么?
沒錯,就是 頁面 的 watcher!
所以這里,D 就會收集到 頁面的 watcher 了,所以就會直接通知 頁面 watcher
看累了嗎.....
你會問了,為什么 Dep.target 是 頁面 watcher?
你好,這里內容就有點多了有點繁雜了,坐好了兄die
先送你一份源碼大禮,收好了
Watcher.prototype.get = function() { // 改變 Dep.target pushTarget() // getter 就是 watcher 回調 var value = this.getter.call(this.vm, this.vm); // 恢復前一個 watcher popTarget() return value }; Dep.target = null; var targetStack = []; function pushTarget(_target) { // 把上一個 Dep.target 緩存起來,便于后面恢復 if (Dep.target) { targetStack.push(Dep.target); } Dep.target = _target; } function popTarget() { Dep.target = targetStack.pop(); }
注解幾個詞
1、頁面 watcher.getter 保存 頁面更新函數,computed watcher.getter 保存 計算getter
2、watcher.get 用于執行 watcher.getter 并 設置 Dep.target
3、Dep.target 會有緩存
下面開始 月老牽線的 詳細流程
1、頁面更新,讀取 computed 的時候,Dep.target 會設置為 頁面 watcher。
2、computed 被讀取,createComputedGetter 包裝的函數觸發,第一次會進行計算
computed-watcher.evaluted 被調用,進而 computed-watcher.get 被調用,Dep.target 被設置為 computed-watcher,舊值 頁面 watcher 被緩存起來。
3、computed 計算會讀取 data,此時 data 就收集到 computed-watcher
同時 computed-watcher 也會保存到 data 的依賴收集器 dep(用于下一步)。
computed 計算完畢,釋放Dep.target,并且Dep.target 恢復上一個watcher(頁面watcher)
4、手動 watcher.depend, 讓 data 再收集一次 Dep.target,于是 data 又收集到 恢復了的頁面watcher
再額外記一個data改變后續流程
綜上,此時 data 的依賴收集器=【computed-watcher,頁面watcher】
data 改變,正序遍歷通知,computed 先更新,頁面再更新,所以,頁面才能讀取到最新的 computed 值
3、收集所有 computed 的 watcher
從源碼中,你可以看出為每個computed 新建watcher 之后,會全部收集到一個對象中,并掛到實例上
為什么收集起來,我暫時的想法是
為了在 createComputedGetter 獲取到對應的 watcher
其實可以通過傳遞 watcher ,但是這里的做法是傳遞 key,然后使用key 去找到對應 watcher
哎喲,我的媽,終于寫完了,瞧瞧多少字,7000多,寫得我
場外:尼瑪大部分是源碼好嗎
okok,行行行
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/105381.html
摘要:如果沒有緩存,我們將不可避免的多次執行的現在我們要開始講解,是如何判斷是否使用緩存的首先計算后,會把計算得到的值保存到一個變量中。當使用緩存時,就直接返回這個變量。 寫文章不容易,點個贊唄兄弟專注 Vue 源碼分享,文章分為白話版和 源碼版,白話版助于理解工作原理,源碼版助于了解內部詳情,讓我們一起學習吧研究基于 Vue版本 【2.5.17】 如果你覺得排版難看,請點擊 下面鏈接 或...
寫文章不容易,點個贊唄兄弟專注 Vue 源碼分享,文章分為白話版和 源碼版,白話版助于理解工作原理,源碼版助于了解內部詳情,讓我們一起學習吧研究基于 Vue版本 【2.5.17】 如果你覺得排版難看,請點擊 下面鏈接 或者 拉到 下面關注公眾號也可以吧 【Vue原理】Props - 源碼版 今天記錄 Props 源碼流程,哎,這東西,就算是研究過了,也真是會隨著時間慢慢忘記的。 幸好我做...
寫文章不容易,點個贊唄兄弟專注 Vue 源碼分享,文章分為白話版和 源碼版,白話版助于理解工作原理,源碼版助于了解內部詳情,讓我們一起學習吧研究基于 Vue版本 【2.5.17】 如果你覺得排版難看,請點擊 下面鏈接 或者 拉到 下面關注公眾號也可以吧 【Vue原理】Mixins - 源碼版 今天探索的是 mixins 的源碼,mixins 根據不同的選項類型會做不同的處理 篇幅會有些長,...
摘要:所以我今后打算把每一個內容分成白話版和源碼版。有什么錯誤的地方,感謝大家能夠指出響應式系統我們都知道,只要在實例中聲明過的數據,那么這個數據就是響應式的。什么是響應式,也即是說,數據發生改變的時候,視圖會重新渲染,匹配更新為最新的值。 寫文章不容易,點個贊唄兄弟專注 Vue 源碼分享,文章分為白話版和 源碼版,白話版助于理解工作原理,源碼版助于了解內部詳情,讓我們一起學習吧研究基于 V...
摘要:當東西發售時,就會打你的電話通知你,讓你來領取完成更新。其中涉及的幾個步驟,按上面的例子來轉化一下你買東西,就是你要使用數據你把電話給老板,電話就是你的,用于通知老板記下電話在電話本,就是把保存在中。剩下的步驟屬于依賴更新 寫文章不容易,點個贊唄兄弟專注 Vue 源碼分享,文章分為白話版和 源碼版,白話版助于理解工作原理,源碼版助于了解內部詳情,讓我們一起學習吧研究基于 Vue版本 【...
閱讀 2471·2021-11-24 09:39
閱讀 3517·2019-08-30 15:53
閱讀 594·2019-08-29 15:15
閱讀 2902·2019-08-26 13:23
閱讀 3211·2019-08-26 10:48
閱讀 642·2019-08-26 10:31
閱讀 748·2019-08-26 10:30
閱讀 2359·2019-08-23 18:32