内心旁白:计算属性有点绕呀,看了好几遍了
1 计算属性
首先 Vue 中定义了 3 个 watcher
- render watcher:模板依赖并且需要显示在视图上变量,其内部保存了一个 render watcher
- computed watcher:计算属性内部保存了一个 computed watcher
- user watcher:使用 watch 侦听的属性内部保存了一个 user watcher
render watcher 之前的文章已经分析过了
下面会分析一下 computed watcher 的实现原理
2 计算属性的实现
一个计算属性的实现分为2部分
- 实例化一个 computed watcher
- 定义计算属性的 getter 函数
2.1 创建 computed watcher
2.1.1 计算属性的两种写法的差异
首先计算属性有两种写法:直接写为 function 和 完整写法
1 | computed: { |
如何求得 fullname 值,第一种方法可以拿来直接用,第二种需要拿到它的get
2.1.2 实例化一个 computed watcher
源码路径:vue-main\src\core\instance\state.ts
在初始化当前组件时,会执行 initComputed
方法初始化计算属性
首先,该方法中,首先遍历用户写的计算属性
拿到计算 fullname 属性的方法:
1 | // 计算属性的多种写法: 直接写function / 完整写法,包含get、set |
然后,会进行实例化
1 | const computedWatcherOptions = { lazy: true } |
new Watcher 时,传入用户定义的求值函数(getter),并传入 lazy: true 表示该 watcher 是 computed watcher
2.1.3 重新劫持计算属性的 get
模板读取计算属性时,需要有计算属性自己的求值逻辑,所以要劫持它的 get
createComputedGetter 方法重新定义了计算属性的 get
当计算属性 fullname 求值时,会走这里
1 | function createComputedGetter(key) { |
这个方面里面也涉及到了 实现计算属性 的几个关键变量和方法
dirty
计算属性使用 dirty 属性来标记该数据是否是脏数据
如果是脏数据,需要重新计算值
否则可以直接使用旧值
dirty 默认为 true
watcher.evaluate()
evaluate() 是计算属性专用的方法,其实方法很简单
首先使用 watcher 用来求值的通用方法: get() (计算属性入栈、求值、出栈)
然后将 dirty 标记:不是脏数据
1 | evaluate() { |
watcher.depend()
让 计算属性watcher中依赖的属性 也去收集栈中的其他watcher
它会遍历当前 computed watcher 的deps属性,依次执行 dep 的 depend 方法
知道了几个重要概念,下面举个例子更容易理解它的整体实现逻辑
3 举个栗子
举例子之前首先得明确3点:
- computed watcher的 evaluate() 函数只是求值,标记dirty
- computed watcher的 update() 函数只是标记dirty为脏
- render watcher才会真正更新视图
3.1 初始化
1 | 定义计算属性: |
首先,组件初始化时,已经有一个 render watcher 了。
dep 中 targetStack 栈会维护着 [render watcher]
然后,实例化一个 computed watcher,computed watcher 不会执行默认的求值操作
当模板中使用计算属性时
1 | {{ fullname }} |
才会执行求值操作,这时计算属性独有 getter 拦截,因为 dirty 默认为 true,会进入 watcher.evaluate() 方法
watcher.evaluate() 方法做了两件事:get()、将 dirty 置为false
- get():
watcher通用求值方法,求fullname
计算属性入栈,targetStack: [render watcher, computed watcher],Dep.target 指向 computed watcher
调用回调函数(用户定义的计算属性函数)求 fullname 值,也就会被 firstname 和 lastname 的 getter 劫持
firstname 和 lastname 的 getterr:也就是会进行依赖收集
firstname 和 lastname 的 dep 会新增computed watcher(因为Dep.target 指向 computed watcher)
计算属性出栈
- 将 dirty 置为false:标记为非脏数据。模板中如果再写 就不用再次求值了,而是使用计算属性的缓存值
到这里求值步骤就结束了
但前面说过了
computed watcher的工作只是求值,标记dirty
render watcher才会真正更新视图
所以应该让 firstname 和 lastname 的 dep 也去收集 render watcher,当依赖的属性变化时能做到更新渲染
所以 computed watcher 的 getter 劫持中,watcher.depend() 方法就起作用了
让 firstname 和 lastname 也去收集 render watcher
所以说:计算属性(fullname)根本不会收集依赖,而是让它依赖的属性(firstname、lastname)去收集依赖
3.2 依赖的属性发生变化
1 | setTimeout(() => { |
只有当计算属性的依赖项被修改时,计算属性才会重新进行计算,生成一个新的值,而视图中其他变量被修改导致视图更新时,计算属性不会重新计算,这是怎么做到的呢?
当计算属性的依赖项,即 firstName 和 lastName 被修改时,数据劫持会触发内部的 setter,执行 watcher 的 update
初始化时,前面的 evaluate
和 depend
方法,firstName 和 lastName 内部的 dep 中都会保存 2 个 watcher,一个 computed watcher,一个 render watcher

1 | update() { |
属于计算属性watcher, lazy 为 true
因此会将 dirty 标记为脏数据
可以看到,computed watcher就做了这么一个简单的事情,将 dirty 标记为脏数据
而真正的求值操作是在 render watcher 中进行的:
执行 render watcher 时
由于视图依赖了 fullName,会触发计算属性重新定义的 getter,执行前面的 createComputedGetter
由于 dirty 为 true 脏数据,则会进行求值逻辑:computed watcher 的 watcher.evaluate()
此时 fullName 就拿到了最新的值了
3.3 非依赖的属性发生变化
如果我修改的是与 fullname 无关的属性
1 | vm.otherTimes = '新值'; |
因为这个属性不会触发计算watcher的
1 | if (this.lazy) { |
因为 dirty 为 false,所以 fullname 不会重新执行计算新值的方法:watcher.evaluate()
4 流程图

About this Post
This post is written by Duan WeiCheng, licensed under CC BY-NC 4.0.