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)
}
}
}
做了兩件事
- 讓i的dep持有a 的watcher實例,完成依賴搜集
- 將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