vue 源码版本是2.6.12
缘起
很多介绍vue源码的文章对computed怎么计算值讲的很清楚,但是对computed 怎么搜集到依赖它的视图渲染watcher,以及怎么去通知对应的渲染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
}
}
}
当组件读取computed a的值的时候会执行 computedGetter函数,先是通过
if (watcher.dirty) {
watcher.evaluate()
}
计算出computed函数的值,然后通过
if (Dep.target) {
watcher.depend()
}
进行依赖搜集。
Dep.target指向当前组件的渲染watcher,进入watcher.depend()看看是怎么进行依赖搜集的
文件位于 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函数中执行this.deps = this.newDeps,所以要看cleanupDeps在哪里被调用的,以及this.newDeps中的值是哪里产生的
/**
* 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函数是在computed 通过watcher.evaluate()计算值的时候被调用的,讲解下这个函数的核心操作
1. pushTarget(this)
这个this是计算属性的watcher,调用dep.js中的
export function pushTarget (target: ?Watcher) {
targetStack.push(target)
Dep.target = target
}
作用是放到栈顶,同时将计算属性的watcher赋值给Dep.taget
2. value = this.getter.call(vm, vm)
会调用 计算属性a的函数
{
return this.i
}
由于引用到了i,所以会触发i的get 函数,就会调用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实例,会执行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]
}
把栈顶的watcher弹出,改变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中的数组中的对象其实就是i的dep实例
再回到 watcher.depend()
depend () {
let i = this.deps.length
while (i--) {
this.deps[i].depend()
}
}
this.deps[i].depend() 这里就是执行
depend () {
if (Dep.target) {
Dep.target.addDep(this)
}
}
此时Dep.target是组件的渲染watcher,所以实现的逻辑是组件渲染watcher调用addDep(this),其实就是持有i的dep,最终被i搜集到依赖。
转了这么大一圈,实际上是为了让组件的watcher被计算属性中引用的data变量搜集到,这也不难理解,既然组件依赖computed的变化,当然也依赖computed中的值的变化,示例中computed中的值变化来自于i的变化,所以当i变化时,就让去通知计算属性的watcher去重新计算,通知组件watcher重新渲染。
对于data中变量的响应式原理和依赖搜集、派发更新可以参考我的这篇文章
从源码的角度分析Vue视图更新和nexttick机制
参考:
https://ustbhuangyi.github.io/vue-analysis/v2/reactive/getters.html#dep
https://juejin.cn/post/6877451301618352141
网友评论