前言
很多人提起 Vue 中的 computed,第一反应就是计算属性会缓存,那么它到底是怎么缓存的呢?缓存的到底是什么,什么时候缓存会失效,相信还是有很多人对此很模糊。
本文以 Vue 2.6.11 版本为基础,就深入原理,带你来看看所谓的缓存到底是什么样的。
注意
本文假定你对 Vue 响应式原理已经有了基础的了解,如果对于 Watcher
、Dep
和什么是 渲染watcher
等概念还不是很熟悉的话,可以先找一些基础的响应式原理的文章或者教程看一下
注意,这篇文章里我也写了 computed 的原理,但是这篇文章里的 computed 是基于 Vue 2.5 版本的,和当前 2.6 版本的变化还是非常大的,可以仅做参考。
示例
按照我的文章惯例,还是以一个最简的示例来演示。
<div id="app">
<span @click="change">{
{sum}}</span>
</div>
<script src="./vue2.6.js"></script>
<script>
new Vue({
el: "#app",
data() {
return {
count: 1,
}
},
methods: {
change() {
this.count = 2
},
},
computed: {
sum() {
return this.count + 1
},
},
})
</script>
复制代码
这个例子很简单,刚开始页面上显示数字 2
,点击数字后变成 3
。
解析
回顾 watcher 的流程
进入正题,Vue 初次运行时会对 computed 属性做一些初始化处理,首先我们回顾一下 watcher 的概念,它的核心概念是 get
求值,和 update
更新。
-
在求值的时候,它会先把自身也就是 watcher 本身赋值给
Dep.target
这个全局变量。 -
然后求值的过程中,会读取到响应式属性,那么响应式属性的 dep 就会收集到这个 watcher 作为依赖。
-
下次响应式属性更新了,就会从 dep 中找出它收集到的 watcher,触发
watcher.update()
去更新。
所以最关键的就在于,这个 get
到底用来做什么,这个 update
会触发什么样的更新。
在基本的响应式更新视图的流程中,以上概念的 get
求值就是指 Vue 的组件重新渲染的函数,而 update
的时候,其实就是重新调用组件的渲染函数去更新视图。
而 Vue 中很巧妙的一点,就是这套流程也同样适用于 computed 的更新。
初始化 computed
这里先提前剧透一下,Vue 会对 options 中的每个 computed 属性也用 watcher 去包装起来,它的 get
函数显然就是要执行用户定义的求值函数,而 update
则是一个比较复杂的流程,接下来我会详细讲解。
首先在组件初始化的时候,会进入到初始化 computed 的函数
if (opts.computed) { initComputed(vm, opts.computed); }
复制代码
进入 initComputed
看看
var watchers = vm._computedWatchers = Object.create(null);
// 依次为每个 computed 属性定义
for (const key in computed) {
const userDef = computed[key]
watchers[key] = new Watcher(
vm, // 实例
getter, // 用户传入的求值函数 sum
noop, // 回调函数 可以先忽视
{ lazy: true } // 声明 lazy 属性 标记 computed watcher
)
// 用户在调用 this.sum 的时候,会发生的事情
defineComputed(vm, key, userDef)
}
复制代码
首先定义了一个空的对象,用来存放所有计算属性相关的 watcher,后文我们会把它叫做 计算watcher
。
然后循环为每个 computed 属性生成了一个 计算watcher
。
它的形态保留关键属性简化后是这样的