computed/watch 源码上差异

130 阅读3分钟

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
    • 有自己的cb所以不用渲染函数调用也会执行
  • 执行逻辑
    • 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 不能写成 => 函数
      • 其实就是 cb.appley(vm, [])
    • opt.sync (文档里没有)可以不用等 queueWatcher 直接触发cb,所以它会比视图更新快