Vue computed 大家都知道有缓存机制,那他是如何实现的,并且如何收集依赖的呢? 一起来看看吧~
Vue 相关
讲的不完美,很多确缺点,主要部分会用特殊颜色标注!
- 了解 computed 的基本用法
let data = function(){
return {
data1: 1,
}
};
let computed = {
computed1: function(){
// 不可以用箭头函数 否则 this 不是指向 vue 实例
return this.data1 * 1
},
computed2: {
get(){
return this.data1
},
set(newVal,oldVal){
this.data1 = newVal
}
}
};
- 主要功能讲解,此阶段时初始化阶段,不是访问 computed 属性阶段
- 在 init computed 属性时,首先会在 vm 绑定 computedWatchers 属性,为一个空对象
- 接下来 会判断属性是否是'function',且获取 computed 的 get属性
- 为上面创建的 computedWatchers 对象添加属性 key 为 computed 中的key,值为 computedWatcher(及new Watcher)
- new Watcher 是,把第3步获取的 get 属性作为 watcher 的 getter,并标记此 watcher 是 lazy,表示computedWatcher
- 重构 computed 的 get,让 get 通过 第3步创建的 computedWatcher 来收集依赖和缓存value
【重构get函数源码】(点击展开)
```js function createComputedGetter(key) {
return function computedGetter() {
const watcher = this._computedWatchers && this._computedWatchers[key]
if (watcher) {
if (watcher.dirty) {
watcher.evaluate() // 执行完后 Dep.targer 不是当前 computedWatcher
}
if (Dep.target) { // 有值的清空是比如有另一个 computed 或者 渲染watcher 访问当当前的 computed 如: computed 的基本用法中 computed2 的例子
watcher.depend()
}
return watcher.value
}
}
}
```
访问 computed 属性时 发生了什么?
- 在 get 执行时,会先获取当前 compute 的 watcher
- 判断 watcher 是否是脏的,也就是依赖有没有变化
- 如果是脏数据,就让当前的 watcher 执行 get
- watcher 执行 get 就会把当前的 computedWatcher 赋值给全局 Dep.target ,然后执行 computed【key】的 get
- 在执行computed get 时, 就会访问到 数据劫持过的 data 数据,就会把访问的 data 属性 添加到 全局的 Dep.target(computedWatcher)依赖中
- 然后 watcher 在 get 最后还会执行 cleanupDeps,就是清理旧的依赖 '(会维护两个 deps 数组)'
- 把全局的 Dep.target 退回到上一个 watcher
- watcher 执行完 get 后 把当前的 'wathcer.dirty 标记为 false' ,判断 全局的 Dep.target 是否有值, 如果有值,就执行 watcher.depend 来完成 当前 computedWatcher 所依赖的 dep 【注意1:此时收集 data属性中 dep 的 watcher 不是 当前访问的 computedWatcher,原理下方有说明 】
- 返回 getter 执行完得到的 value 并返回 value
computed 的缓存主要重写了 get 属性,并为 computed[key] 创建了 watcher 实例(computedWatcher),看 computedWatcher 是否是脏的这个标记, 如果是脏的(表示依赖被修改过),就重新执行 computedWatcher 的 get 否则直接返回 computedWatcher.value
注意的解释
注意1: 在 computed get 执行完后, 判断当前的全局 Dep.target 是否有值 有值才收集 computedWatcher 的依赖的原因:
- 访问 computed 的依赖项可能没有被 其他依赖所依赖, 比如 computed1 中依赖了 computed2 属性, 那 computed1 没有收集到 computed2 所依赖的 dep 当 computed2 所依赖的 属性改变时, 会通知不到
- 访问 computed 的依赖项可能没有被 渲染watcher 所依赖, 因为不是所有的 data 属性都会用在渲染模板里面被访问
Watcher对象维护两个与依赖(Dep)相关的数组:deps和newDeps。这两个数组服务于不同的目的,主要是为了在Vue实例生命周期的不同阶段(尤其是组件更新过程)有效地管理依赖关系。比较官方的说明它们各自的职责和使用场景:
deps数组:
- 作用:存储当前Watcher实例在初始化或执行时收集到的所有依赖(Dep对象)。这些依赖通常是组件模板中使用的数据绑定表达式所对应的依赖。当这些依赖(即数据源)发生变化时,Dep对象会通知其所有者Watcher进行更新。
- 生命周期:
deps数组主要在组件渲染期间发挥作用,即在创建Watcher实例时(首次渲染或组件更新时)通过遍历模板并访问数据属性触发依赖收集,将对应的Dep对象添加到deps数组中。在组件的整个生命周期内,deps数组通常保持不变,除非组件再次进入更新流程。 - 通知更新:当Dep对象内部的数据发生变化时,会触发其
notify方法,遍历并调用deps数组中所有Watcher实例的update方法,启动Vue的更新流程。
newDeps数组:
- 作用:在组件进行更新时,新的依赖收集阶段临时存放新收集到的依赖(Dep对象)。Vue在进行组件更新时,会重新遍历模板并访问数据属性,这个过程会触发新的依赖收集。新收集到的依赖被暂时保存在
newDeps数组中。 - 生命周期:
newDeps数组仅在组件更新的特定阶段(即依赖重新收集阶段)存在,用于记录本次更新中新产生的依赖关系。在依赖收集阶段结束后,Vue会比较deps和newDeps,执行必要的依赖关系管理操作(如移除已不存在的依赖、添加新依赖),然后清空newDeps数组。 - 依赖关系管理:比较
deps和newDeps的目的在于处理依赖关系的变化。如果某个旧依赖(在deps中)在新收集阶段未出现(不在newDeps中),说明该依赖不再被当前Watcher所关注,应将其从deps中移除,释放相关资源。反之,如果newDeps中存在deps中没有的依赖,则将其添加到deps中,确保Watcher能够接收到新依赖的变化通知。
综上所述,Watcher维护两个dep数组的原因在于:
deps数组用于持久存储当前Watcher在正常运行时需要监听的稳定依赖关系,确保在数据变化时能及时得到通知并触发相应的更新流程。newDeps数组则是在组件更新过程中临时存放新收集到的依赖关系,用于对比和管理依赖关系的变化,确保Watcher依赖列表的准确性和有效性,避免无效监听和资源浪费。在依赖关系处理完毕后,newDeps数组会被清空,等待下一次更新时重新使用。