computed原理
# computed 原理
computed 实质是一个惰性的 watcher
计算属性可以通过闭包进行传参,传参的计算属性结果并没有缓存,而是重新进行了计算
# computed 源码分析
- 根据 computed 语法取 定义的 computed 属性的 getter
如果是函数,则该函数默认为取值器 getter
如果是一个对象,则取对象中的 get 属性作为取值器赋给变量 getter
- 为每个 computed 属性生成对应的 Watcher 实例
创建一个 watcher 实例,并将当前循环到的 computed 属性名作为键,创建的 watcher 实例作为值存入 watchers 对象
Watcher 实例的作用:
(1)把用户设置的 computed-getter,存放到 watcher.getter 中,用于后面的计算
(2)watcher.value 存放计算结果
(3)控制缓存计算结果是否有效:computed 新建 watcher 的时候,传入 lazy, lazy 赋值给了 dirty,dirty 控制缓存计算结果是否有效
当 dirty 为 true 时,读取 computed 会执行 get 函数,重新计算。
当 dirty 为 false 时,读取 computed 会使用缓存。
- 把每个 computed 属性通过 Object.defineProperty 变成响应式对象
将 computed 属性添加到组件实例, 通过 get、set 进行属性值的获取或设置,并且重新定义 getter 方法
# Computed 如何控制缓存
为什么需要缓存?
假设我们有一个性能开销比较大的计算属性 A,它需要遍历一个巨大的数组并做大量的计算。然后我们可能有其他的计算属性依赖于 A 。如果没有缓存,我们将不可避免的多次执行 A 的 get 函数。大量的计算将导致 JS 线程被占用,阻塞页面的渲染。
computed 是如何判断是否使用缓存的呢?
首先 computed 计算后,会把计算得到的值保存到一个变量(watcher.value)中。读取 computed 并使用缓存时,就直接返回这个变量。当 computed 更新时,就会重新赋值更新这个变量。
computed 计算就是调用你设置的 get 函数,然后得到返回值。
computed 控制缓存的重要一点是 【脏数据标志位 dirty】 dirty 是 watcher 的一个属性。
(1) 当 dirty 为 true 时,读取 computed 会执行 get 函数,重新计算。
(2) 当 dirty 为 false 时,读取 computed 会使用缓存。
缓存机制简述
一开始每个 computed 新建自己的 watcher 时,会设置 watcher.dirty = true,以便于 computed 被使用时,会计算得到值
当依赖的数据变化了,通知 computed 时,会赋值 watcher.dirty = true,此时重新读取 computed 时,会执行 get 函数重新计算。
computed 计算完成之后,会设置 watcher.dirty = false,以便于其他地方再次读取时,使用缓存,免于计算。
# computed 触发场景
场景:页面 A 引用了 computed B,computed B 依赖了 data C。
# 页面初始化时
页面初始化时,会读取 computed 属性值,触发重新定义的 getter,由于观察者的 dirty 值为 true,将会调用原始的 getter 函数,当 getter 方法读取 data 数据时会触发原始的 get 方法(数据劫持中的 get 方法),将 computed 对应的 watcher 添加到 data 依赖收集器(dep)中。观察者的 get 方法执行完后,更新观察者的 value,并将 dirty 置为 false,表示 value 值已更新,之后执行观察者的 depend 方法,将上层观察者也添加到 getter 函数中 data 的依赖收集器(dep)中,最后返回 computed 的 value 值;
# computed 属性 getter 函数依赖的 data 改变时
通知 computed 的 watcher 更新,只会重置 脏数据标志位 dirty =true,不会计算值。
通知 页面 watcher 进行更新渲染,进而重新读取 computed ,然后 computed 开始重新计算。
当更改了 computed 属性 getter 函数依赖的 data 值时,将会触发之前 dep 收集的 watcher,依次调用 watcher 的 update 方法,先调用 computed 的观察者的 update 方法,由于 lazy 为 true,会将 dirty 先设置为 true,表示 computed 属性 getter 函数依赖 data 发生变化,但不调用观察者的 get 方法更新 value 值。这时调用包含更新页面方法的观察者的 update 方法,在更新页面时会读取 computed 属性值,触发重新定义的 getter 函数,由于 dirty 为 true,调用该观察者的 get 方法,更新 value 并返回,完成页面渲染;
为什么 data C 能通知 页面 A?
data C 的依赖收集器会同时收集到 computed B 和 页面 A 的 watcher。
为什么 data C 能收集到 页面 A 的 watcher?
在 页面 A 在读取 computed B 的时候,趁机把 页面 A 的 watcher 塞给了 data C ,于是 页面 A watcher 和 data C 间接地关联在了一起,于是 data C 就会收集到 页面 A watcher。
# computed 问题
# computed 与 method 的区别
computed 是属性访问,而 methods 是函数调用
computed 带有缓存功能,只有在它的相关依赖发生改变时才会重新求值;而 methods 调用总会执行该函数。
# watch 与 computed 的区别
- 缓存支持
watch 不支持缓存,数据变,直接会触发相应的操作;
computed 支持缓存,只有依赖数据发生改变,才会重新进行计算
- 异步支持
watch 支持异步;
computed 不支持异步,当 computed 内有异步操作时无效,无法监听数据的变化
- 使用场景
watch 当需要在数据变化时执行异步或开销较大的操作时,应该使用 watch,使用 watch 选项允许执行异步操作 ( 访问一个 API ),限制执行该操作的频率,并在得到最终结果前,设置中间状态。这些都是计算属性无法做到的。
computed 当需要进行数值计算,并且依赖于其它数据时,应该使用 computed,因为可以利用 computed 的缓存特性,避免每次获取值时都要重新计算。
# computed 计算值为什么还可以依赖另外一个 computed 计算值
https://www.cnblogs.com/yangzhou33/p/13809534.html (opens new window)