vue2中computed原理
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了vue2中computed原理相关的知识,希望对你有一定的参考价值。
参考技术A 要理解这个,首先要理解vue2的数据响应式原理,因为computed这个API的实现是建立在数据响应式的基础之上的。这里附上vue响应式原理的地址: vue2数据响应式原理
在vue的watcher实例中配置了lazy,dirty,value属性,就是用来配合实现computed的API。vue在初始化computed时,会给每一个computed属性配置一个watcher,并配置lazy的值为true。在new Watcher时,还会设置dirty为true。由于lazy为true,这个时候并不会执行computed配置的get方法,也就是说不会去计算出这个计算属性的值出来,那么什么时候才计算呢?就是在执行render函数时,会去取计算属性的值,这个时候,会执行计算属性的getter。在getter里面,会先判断dirty的值(为true,则表示当前计算属性的值为脏值,已经过期了,需要重新计算出来;为false则表示当前的值是最新的,不需要重新计算,直接取缓存里面的值就行了,也就是value字段的值)。如果为true,则调用watcher.evaluate方法计算最新的值出来,然后将dirty设为false,把最新的值赋值给value。在计算最新的值时,也就是执行我们在computed对象中配置的相应的get函数。根据之前讲的响应式原理,会先将当前的watcher挂到dep的静态属性target上,然后执行get,在这个过程中,会使用到data中的属性,然后进行依赖收集,将computed的watcher存到data数据对应的dep中去。完了之后,将watcher从targetStack中弹出,这是dep的静态属性target的值又变成了render函数的watcher。以上步骤执行完后,会判断当前dep的target是否还有值,有的话,会执行watcher.depend()。这一步是为了让依赖的data对应的dep中不仅是收集到computed的wathcer,还要收集render函数的watcher,因为当我们依赖的data改变时,不仅要通知到computed的watcher,还要通知render函数的watcher,达到重渲染的目的。然后会把最新的value值返回,这时,render函数里面终于拿到了计算属性的最新值了。假如后面继续用到了这个计算属性,那么在执行计算属性的getter时,跟之前一样,会先判断dirty值,这个时候,dirty的值为false,则直接返回value值,也就是之前说的取缓存的值,不会再次运行一遍计算属性的get函数了。
当某个时候,计算属性依赖的data数据变了,则会先后触发computed的watcher,还有render函数的watcher。在将vue的数据响应式原理时,我们知道,数据改变时,是会触发watcher的update方法。在这个方法里面,首先判断lazy是否为true,这就是针对计算属性设计的。我们可以看看源码片段:
如果为true,则只做了一件非常简单的事,就是把dirty设为了true,然后就没了。这个时候会去执行render函数的watcher.update。也就是把render函数的执行交给了nextTick去管理,这也是之前讲过的。在执行render函数时,又使用到了这个计算属性,那么,则会执行这个计算属性的getter,判断dirty是否为true,由于刚刚在执行computed的wathcer.update时,把dirty设为了true,这个时候肯定会执行watcher.evaluate重新去计算最新的值,也就是执行我们配置在computed里面的get函数。接下来的其实跟前面所讲的都是一样的了。
这里附上关于computed初始化的源码片段:
以上就是个人对于computed原理的理解了。
vue computed 实现原理与 watch 对比
原理
<div id="app">
{{num}}
{{num1}}
</div>
var app = new Vue({
el: '#app',
data: {
num: 1
},
computed: {
num1 () {
return this.num + 1;
}
}
});
console.log(app);
computed 本质是一个惰性求值的订阅者。data 属性的 Observer 挂在 _data
属性下,而 computed 属性挂在 _computedWatchers
下。而发布者 Dep 里存放了两个订阅者,而和computed相关的订阅者,其实只做了一件事情,标记 dirty 为 true,等待 get 时再真正计算。
computed 内部实现了一个惰性的 watcher,也就是 _computedWatchers,_computedWatchers 不会立刻求值,同时持有一个 dep 实例。
其内部通过 this.dirty 属性标记计算属性是否需要重新求值
变更流程如下:
- 当 computed 的依赖的状态发生改变时,就会通知这个惰性的 watcher。
- computed watcher 通过 this.dep.subs.length 判断有没有订阅者,
- 有的话,会重新计算,然后对比新旧值,如果变化了,会重新渲染。 (Vue 想确保不仅仅是计算属性依赖的值发生变化,而是当计算属性最终计算的值发生变化时才会触发渲染 watcher 重新渲染,本质上是一种优化。)
- 没有的话,仅仅把
this.dirty = true
。 (当计算属性依赖于其他数据时,属性并不会立即重新计算,只有之后其他地方需要读取属性的时候,它才会真正计算,即具备 lazy(懒计算)特性。)
watch
这是 computed 的 watcher 观察者结构。特有的 lazy 为 true 代表 getter 时需要计算更新,注意 cb 只是一个空的函数,啥也不做,但是 expression 有值
Watcher {
vm: Vue {_uid: 0, _isVue: true, $options: {…}, _renderProxy: Proxy, _self: Vue, …}
deep: false
user: false
lazy: true
sync: false
before: undefined
cb: function noop(a, b, c) {}
id: 1
active: true
dirty: false
deps: (2) [Dep, Dep]
newDeps: []
depIds: Set(2) {3, 6}
newDepIds: Set(0) {}
expression: "len() {? return this.message.length? }"
getter: ? len()
value: 2
__proto__: Object
}
这是 watch 的 观察者结构,注意 user 为 true,expression 里没有关键内容,但是 cb 里有,更新值的时候就是判定 user 为 true 时,调用 cb 获取新值。
Watcher {
vm: Vue {_uid: 0, _isVue: true, $options: {…}, _renderProxy: Proxy, _self: Vue, …}
deep: false
user: true
lazy: false
sync: false
before: undefined
cb: function message() {
this.full = this.message.join(',');
}
id: 2
active: true
dirty: false
deps: (2) [Dep, Dep]
newDeps: []
depIds: Set(2) {3, 6}
newDepIds: Set(0) {}
expression: "message"
getter: ? (obj)
value: (2) ["a", "c", __ob__: Observer],
__proto__: Object
}
与 watch 有什么区别
computed 计算属性 : 依赖其它属性值,并且 computed 的值有缓存,只有它依赖的属性值发生改变,下一次获取 computed 的值时才会重新计算 computed 的值。
watch 侦听器 : 更多的是「观察」的作用,无缓存性,类似于某些数据的监听回调,每当监听的数据变化时都会执行回调进行后续操作。
与 watch 运用场景的对比
需要在数据变化时执行异步或开销较大的操作时,应该使用 watch,使用 watch 选项允许我们执行异步操作 ( 访问一个 API ),限制我们执行该操作的频率,并在我们得到最终结果前,设置中间状态。这些都是计算属性无法做到的。
当我们需要进行数值计算,并且依赖于其它数据时,应该使用 computed,因为可以利用 computed 的缓存特性,避免每次获取值时,都要重新计算。
以上是关于vue2中computed原理的主要内容,如果未能解决你的问题,请参考以下文章