從源碼的角度分析vue computed的依賴搜集

vue 源碼版本是2.6.12

緣起

很多介紹vue源碼的文章對computed怎么計算值講的很清楚,但是對computed 怎么搜集到依賴它的視圖渲染watcher,以及怎么去通知對應(yīng)的渲染watcher去更新講解的很模糊或者干脆一筆帶過侨赡。這篇文章主要講解——computed watcher是怎么搜集到訂閱它的渲染watcher暴匠。

<template>
    <div>   
        <div>{{a}}</div>
    </div>
</template>
<script>
export default {
    data() {
        return {
            i: 0
        }
    }, 
    computed: {
        a() {
            return this.i
        }
    },
}

依賴搜集

文件在src/core/instance/state.js

function createComputedGetter (key) {
  return function computedGetter () {
    const watcher = this._computedWatchers && this._computedWatchers[key]
    if (watcher) {
      if (watcher.dirty) {
        watcher.evaluate()
      }
      if (Dep.target) {
        watcher.depend()
      }
      return watcher.value
    }
  }
}

當(dāng)組件讀取computed a的值的時候會執(zhí)行 computedGetter函數(shù),先是通過

      if (watcher.dirty) {
        watcher.evaluate()
      }

計算出computed函數(shù)的值券坞,然后通過

      if (Dep.target) {
        watcher.depend()
      }

進(jìn)行依賴搜集占贫。
Dep.target指向當(dāng)前組件的渲染watcher桃熄,進(jìn)入watcher.depend()看看是怎么進(jìn)行依賴搜集的
文件位于 src/core/observer/watcher.js

  /**
   * Depend on all deps collected by this watcher.
   */
  depend () {
    let i = this.deps.length
    while (i--) {
      this.deps[i].depend()
    }
  }

第一個問題:this.deps的賦值

  /**
   * Clean up for dependency collection.
   */
  cleanupDeps () {
    let i = this.deps.length
    while (i--) {
      const dep = this.deps[i]
      if (!this.newDepIds.has(dep.id)) {
        dep.removeSub(this)
      }
    }
    let tmp = this.depIds
    this.depIds = this.newDepIds
    this.newDepIds = tmp
    this.newDepIds.clear()
    tmp = this.deps
    this.deps = this.newDeps
    this.newDeps = tmp
    this.newDeps.length = 0
  }

是在cleanupDeps函數(shù)中執(zhí)行this.deps = this.newDeps,所以要看cleanupDeps在哪里被調(diào)用的型奥,以及this.newDeps中的值是哪里產(chǎn)生的

  /**
   * Evaluate the getter, and re-collect dependencies.
   */
  get () {
    pushTarget(this)
    let value
    const vm = this.vm
    try {
      value = this.getter.call(vm, vm)
    } catch (e) {
      if (this.user) {
        handleError(e, vm, `getter for watcher "${this.expression}"`)
      } else {
        throw e
      }
    } finally {
      // "touch" every property so they are all tracked as
      // dependencies for deep watching
      if (this.deep) {
        traverse(value)
      }
      popTarget()
      this.cleanupDeps()
    }
    return value
  }

get函數(shù)是在computed 通過watcher.evaluate()計算值的時候被調(diào)用的瞳收,講解下這個函數(shù)的核心操作

1. pushTarget(this)

這個this是計算屬性的watcher,調(diào)用dep.js中的

export function pushTarget (target: ?Watcher) {
  targetStack.push(target)
  Dep.target = target
}

作用是放到棧頂桩引,同時將計算屬性的watcher賦值給Dep.taget

2. value = this.getter.call(vm, vm)

會調(diào)用 計算屬性a的函數(shù)

     {
            return this.i
        }

由于引用到了i缎讼,所以會觸發(fā)i的get 函數(shù),就會調(diào)用dep.depend()坑匠,實際上是i的依賴搜集,這里的dep對象屬于i

    get: function reactiveGetter () {
      const value = getter ? getter.call(obj) : val
      if (Dep.target) {
        dep.depend()
        if (childOb) {
          childOb.dep.depend()
          if (Array.isArray(value)) {
            dependArray(value)
          }
        }
      }
      return value
    },

dep.depend() 位于src/core/observer/dep.js

  depend () {
    if (Dep.target) {
      Dep.target.addDep(this)
    }
  }

這里的Dep.target就是上面保存的computed watcher實例卧惜,會執(zhí)行watcher中的addDep厘灼,這里的this就是i的dep實例
文件位于 src/core/observer/watcher.js

  /**
   * Add a dependency to this directive.
   */
  addDep (dep: Dep) {
    const id = dep.id
    if (!this.newDepIds.has(id)) {
      this.newDepIds.add(id)
      this.newDeps.push(dep)
      if (!this.depIds.has(id)) {
        dep.addSub(this)
      }
    }
  }

做了兩件事

  1. 讓i的dep持有a 的watcher實例,完成依賴搜集
  2. 將i的dep實例存到this.newDeps
3.popTarget()
export function popTarget () {
  targetStack.pop()
  Dep.target = targetStack[targetStack.length - 1]
}

把棧頂?shù)膚atcher彈出咽瓷,改變Dep.target的指向设凹,此時指向組件的渲染watcher

4.cleanupDeps
  cleanupDeps () {
    let i = this.deps.length
    while (i--) {
      const dep = this.deps[i]
      if (!this.newDepIds.has(dep.id)) {
        dep.removeSub(this)
      }
    }
    let tmp = this.depIds
    this.depIds = this.newDepIds
    this.newDepIds = tmp
    this.newDepIds.clear()
    tmp = this.deps
    this.deps = this.newDeps
    this.newDeps = tmp
    this.newDeps.length = 0
  }

這一步就是 將this.newDeps的值賦給this.deps,此時this.deps中的數(shù)組中的對象其實就是i的dep實例

再回到 watcher.depend()

  depend () {
    let i = this.deps.length
    while (i--) {
      this.deps[i].depend()
    }
  }

this.deps[i].depend() 這里就是執(zhí)行

  depend () {
    if (Dep.target) {
      Dep.target.addDep(this)
    }
  }

此時Dep.target是組件的渲染watcher茅姜,所以實現(xiàn)的邏輯是組件渲染watcher調(diào)用addDep(this)闪朱,其實就是持有i的dep月匣,最終被i搜集到依賴。
轉(zhuǎn)了這么大一圈奋姿,實際上是為了讓組件的watcher被計算屬性中引用的data變量搜集到锄开,這也不難理解,既然組件依賴computed的變化称诗,當(dāng)然也依賴computed中的值的變化萍悴,示例中computed中的值變化來自于i的變化,所以當(dāng)i變化時寓免,就讓去通知計算屬性的watcher去重新計算癣诱,通知組件watcher重新渲染。
對于data中變量的響應(yīng)式原理和依賴搜集袜香、派發(fā)更新可以參考我的這篇文章
從源碼的角度分析Vue視圖更新和nexttick機(jī)制

參考:
https://ustbhuangyi.github.io/vue-analysis/v2/reactive/getters.html#dep
https://juejin.cn/post/6877451301618352141

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末撕予,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子蜈首,更是在濱河造成了極大的恐慌嗅蔬,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,839評論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件疾就,死亡現(xiàn)場離奇詭異澜术,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)猬腰,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,543評論 2 382
  • 文/潘曉璐 我一進(jìn)店門鸟废,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人姑荷,你說我怎么就攤上這事盒延。” “怎么了鼠冕?”我有些...
    開封第一講書人閱讀 153,116評論 0 344
  • 文/不壞的土叔 我叫張陵添寺,是天一觀的道長。 經(jīng)常有香客問我懈费,道長计露,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,371評論 1 279
  • 正文 為了忘掉前任憎乙,我火速辦了婚禮票罐,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘泞边。我一直安慰自己该押,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 64,384評論 5 374
  • 文/花漫 我一把揭開白布阵谚。 她就那樣靜靜地躺著蚕礼,像睡著了一般烟具。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上奠蹬,一...
    開封第一講書人閱讀 49,111評論 1 285
  • 那天朝聋,我揣著相機(jī)與錄音,去河邊找鬼罩润。 笑死玖翅,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的割以。 我是一名探鬼主播金度,決...
    沈念sama閱讀 38,416評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼严沥!你這毒婦竟也來了猜极?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,053評論 0 259
  • 序言:老撾萬榮一對情侶失蹤消玄,失蹤者是張志新(化名)和其女友劉穎跟伏,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體翩瓜,經(jīng)...
    沈念sama閱讀 43,558評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡受扳,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,007評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了兔跌。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片勘高。...
    茶點故事閱讀 38,117評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖坟桅,靈堂內(nèi)的尸體忽然破棺而出华望,到底是詐尸還是另有隱情,我是刑警寧澤仅乓,帶...
    沈念sama閱讀 33,756評論 4 324
  • 正文 年R本政府宣布赖舟,位于F島的核電站,受9級特大地震影響夸楣,放射性物質(zhì)發(fā)生泄漏宾抓。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,324評論 3 307
  • 文/蒙蒙 一裕偿、第九天 我趴在偏房一處隱蔽的房頂上張望洞慎。 院中可真熱鬧,春花似錦嘿棘、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,315評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽焦人。三九已至,卻和暖如春重父,著一層夾襖步出監(jiān)牢的瞬間花椭,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,539評論 1 262
  • 我被黑心中介騙來泰國打工房午, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留矿辽,地道東北人。 一個月前我還...
    沈念sama閱讀 45,578評論 2 355
  • 正文 我出身青樓郭厌,卻偏偏與公主長得像袋倔,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子折柠,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,877評論 2 345

推薦閱讀更多精彩內(nèi)容