计算属性和watch的原理


Watcher是什么?Watcher的种类有哪些?

大家要注意,这里说的是Watcher,要跟vue里使用的watch属性区分一下哦

1.什么是Watcher呢?

举个例子,请看下面代码:

// 例子代码,与本章代码无关

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<div>{{name}}</div>
aaa
data() {
return {
name: 'aaa'
}
},
computed: {
info () {
return this.name
}
},
watch: {
name(newVal) {
console.log(newVal)
}
}

上方代码可知,name变量被三处地方所依赖,分别是html里,computed里,watch里。只要name一改变,html里就会重新渲染,computed里就会重新计算,watch里就会重新执行。那么是谁去通知这三个地方name修改了呢?那就是Watcher了

2.Watcher的种类有哪些呢?

上面所说的三处地方就刚刚好代表了三种Watcher,分别是:

  1. 渲染Watcher:变量修改时,负责通知HTML里的重新渲染
  2. computed Watcher:变量修改时,负责通知computed里依赖此变量的computed属性变量的修改
  3. user Watcher:变量修改时,负责通知watch属性里所对应的变量函数的执行

Vue–Observer、Dep、Watcher

  1. Observer 将数据定义为响应式,每个 Observer 实例都有自己的 Dep 来管理依赖。实例化 Wacther 的时候进行求值会触发 getter ,进而执行 dep.depend() 将当前 Wacther 加入 Dep 维护的依赖列表,这就是依赖收集过程

  2. 数据发生变化触发 setter 执行 dep.notify,Dep 会执行所有依赖的 update 方法并加入异步更新队列,这就是触发依赖过程。
    博客

4.Watcher为何也要反过来收集Dep?

上面说到了,dep是name的管家,他的职责是:name更新时,dep会带着主人的命令去通知subs里的Watcher去做该做的事,那么,dep收集Watcher很合理。那为什么watcher也需要反过来收集dep呢?

这是因为computed属性里的变量没有自己的dep,也就是他没有自己的管家,看以下例子:

这里先说一个知识点:如果html里不依赖name这个变量,那么无论name再怎么变,他都不会主动去刷新视图,因为html没引用他(说专业点就是:name的dep里没有渲染Watcher),注意,这里说的是不会主动,但这并不代表他不会被动去更新。什么情况下他会被动去更新呢?那就是computed有依赖他的属性变量。

// 例子代码,与本章代码无关

1
2
3
4
5
6
7
<div>{{person}}</div>

computed: {
person {
return `名称:${this.name}`
}
}

这里的person事依赖于name的,但是person是没有自己的dep的(因为他是computed属性变量),而name是有的。好了,继续看,请注意,此例子html里只有person的引用没有name的引用,所以name一改变,按理说虽然person跟着变了,但是html不会重新渲染,因为name虽然有dep,有更新视图的能力,但是奈何人家html不引用他啊!person想要自己去更新视图,但他却没这个能力啊,毕竟他没有dep这个管家!这个时候computed Watcher里收集的name的dep就派上用场了,可以借助这些dep去更新视图,达到更新html里的person的效果。具体会在下面computed里实现。

computed

计算属性实现原理
这里还是按照惯例,将定义的computed属性的每一项使用Watcher类进行实例化,不过这里是按照computed-watcher的形式,来看下如何实例化的:

这里的变量watcher就是之前computed对应的computed-watcher实例,接下来会执行Watcher类专门为计算属性定义的两个方法,在执行evaluate方法进行求值的过程中又会触发computed内可以访问到的响应式数据的get,它们会将当前的computed-watcher作为依赖收集到自己的dep里,计算完毕之后将dirty置为false,表示已经计算过了。
然后执行depend让计算属性内的响应式数据订阅当前的render-watcher,所以computed内的响应式数据会收集computed-watcher和render-watcher两个watcher,当computed内的状态发生变更触发set后,首先通知computed需要进行重新计算,然后通知到视图执行渲染,再渲染中会访问到computed计算后的值,最后渲染到页面。

Ps: 计算属性内的值须是响应式数据才能触发重新计算。

watch总结:为什么计算属性有缓存功能?因为当计算属性经过计算后,内部的标志位会表明已经计算过了,再次访问时会直接读取计算后的值;为什么计算属性内的响应式数据发生变更后,计算属性会重新计算?因为内部的响应式数据会收集computed-watcher,变更后通知计算属性要进行计算,也会通知页面重新渲染,渲染时会读取到重新计算后的值。

参考文章

Vue原理解析(九):搞懂computed和watch原理,减少使用场景思考时间
「Vue源码学习(四)」立志写一篇人人都看的懂的computed,watch原理