computed/watch 源码上差异
computed/watch 源码上差异
- 是在
initState()内初始化,内部创建的watcher早于渲染函数的watcher
- 从代码逻辑上看
watch是比computed来的简单和直接的,因为不像computed有两次依赖收集的动作
- 计算属性适合用在模板渲染中,某个值是依赖了其它的响应式对象甚至是计算属性计算而来
- 侦听属性适用于观测某个值的变化去完成一段复杂的业务逻辑
computed
- 计算属性是惰性(lazy)的,是基于依赖的
this.xxx数据更新而更新。是一个computed watcher类型的 Watcher{lazy: true}
- 需要在渲染函数内调用才会触发,要不就是个无效函数也没有自己的
cb
- 所以它的更新也就依赖于依赖的 data 更新去触发 vm._update()
- 执行逻辑
- initComputed() 创建各自的 new Watcher( lazy -> dirty ) -> defineComputed()
- 这个 watcher 拿着用户定义的计算属性函数
- 由于这个 watcher 配置的
{lazy: true}所以初始化时,并不执行watcher.get()获取真正的value内容
- defineComputed 对配置的 computed 设置 Object.defineProperty() 数据拦截以实现可以 this 上访问到该计算属性并可以赋值修改
- 拦截时会对定义的计算属性包裹 computedGetter() 函数
- computedGetter 实现了对用户定义的计算属性函数的实际触发
watcher.evaluate()。它完成了当前w被依赖的data给收集
- 触发
w.evaluate()前会判断w.dirty状态,这就是控制计算属性的缓存性(重要)
- 之后依赖值更新时派发通知,会先通知计算属性的
w更改dirty = true不更新视图。之后再通知渲染函数更新,这时就会重新w.evaluate()获取值
- 上面只完成了该计算属性的
w被收集,但是页面更新是通过触发render w实现的。所以需要依赖的data也收集render w。这就通过watcher.depend() 实现(重要步骤)
- 两次依赖收集
- 一是为了只用了
computed而没用对应的data,那样更改data并不会触发视图的更新
- 二是计算属性是依赖性和缓冲性的它并不主动更新,而是基于依赖的更新而更新。所以需要让依赖的
data收集渲染函数
- watcher.depend = dep.depend() 在
w.evaluate()时已经持有了data的 dep,所以在 watcher.depend() 时就可以让该data再收集render w了
- set 的逻辑:本身的
w并没有 update() 而是基于依赖的data去触发渲染函数的更新
- 注意: 计算属性依赖是基于配置的函数/get函数里的
data,所以如果配置了set函数但非该data是不会更新内容的
watch
- 配置的 watcher 是个
user watcher类型的 Watcher
- 执行逻辑
- initWatch() -> createWatcher() = vm.$watch -> new Watcher( user )
- 如果 watch 的 handler 是个数组 initWatch 内会遍历 createWatcher 创建各自的 watcher
- opt.deep 通过 traverse(value) 去遍历读取该
value下的所有属性,通过触发 getter 来把w收集。这样后续value下的数据更改都会触发cb
- opt.immediate 通过 invokeWithErrorHandling() 来触发。内部是 handler.apply(context, args) 所以 watch 不能写成 => 函数
- opt.sync (文档里没有)可以不用等 queueWatcher 直接触发
cb,所以它会比视图更新快