肉烂在锅里

个人站

我是软件研发中心培训主管,我喜欢看动漫,学习web前端开发。


computed计算属性

computed计算属性

computed的初始化时什么时候开始的?

答:

在执行new Vue时执行了vm._init()方法,在this._init中又执行了一系列初始化操作:

然后执行initState,在initProps,initMethods和initData后执行了initComputed。

二、为什么computed的初始化要在data和props之后进行?

之所以要initComputed在后,是因为computed中的‘缓存功能’,只有当computed所依赖的变量发生改变时才会重新computed。由于‘依赖的变量’发生变化时,要告知computed,所以‘依赖的变量’也必须是响应式的,因此initState、initData在先也就很好理解了。

三、computed是如何实现监听依赖变化然后重新算在触发模板更新的?

computed为了能感知到内部依赖的变化,就要监听内部依赖,这和render watcher监视data是一个道理,因此Vue在实现computed时也是通过了观察者模式,让每个computed

都持有一个watcher,然后订阅内部依赖的响应是对象的dep。这样当内部的响应式对象发生改变就会触发dep.notify方法,从而触发computed watcher的回调,这样就实现了对内部依赖数据监听的功能。

computed单单根据依赖的变化进行重计算是毫无意义的,computed的重计算需要伴随着dom的更新才有意义,因此render watcher的任务就不仅仅是订阅props、data的dep了。对于computed的dep也要订阅。这也就需要computed属性也持有一个dep实例才行。

Vue巧妙的将computed watcher设计成了一种可以持有dep实例的特殊watcher,一旦持有了dep实例,computed watcher也就有了进行依赖收集和派发更新的能力。

所以大体上讲,computed的响应式实现是这样子的:

computed的实现细节是什么样子的?

首先执行this._init,然后执行this.initState,然后执行initComputed。在initComputed中,主要做了四件事情:

  1. 为vm.computedWatchers创建一个空对象,用来存放输入当前Vue组件实例的全部computed Watcher

  2. 遍历computed配置项,然后获取computed函数。

  3. 用computed函数创建computed watcher并保存在vm.computedWatcers中。

  4. 用defineComputed为computed属性添加getter个setter,同时检查computed的名称是否在data和props中存在。

最终创建的getter函数如下:

由于在初始化computed watcher时带入了computed watcher相关的配置,因此初始化computed watcher时会和初始化普通watcher有所不同。

由于带入了相关配置,this.computed为true。可以看到,computed watcher也持有一个dep实例。需要注意的是,在Watcher构造函数中无论什么watcher都执行了watcher原型上的get方法,也就触发了pushTarget将Dep.target设置为当前的computed watcher。

当模板编译时访问到相关computed时,会触发computed的getter:

此时会进行computed watcher所持有的dep进行依赖收集:

依赖收集的过程之前已经提到过了,这里就不再提了。

然后执行watcher.evaluate()方法去进行求值。在求值的过程中,因为执行了get()方法,因此触发了computed函数,在执行函数的过程中,访问了各个依赖的响应式变量,响应式变量的getter被触发,也就进行了依赖收集将此时全局的computed watcher塞入了自己subs中。此时首次渲染完成。

当依赖的数据发生变化时,由于触发了依赖数据的setter,因此会触发computed watcher的update方法:

getAndInvoke方法也很简单,就是检查新老值是否相同,然后执行回调函数。

这样,订阅了computed watcher 的 dep 实例的render watcher就得到触发,模板更新也就此完成。

打赏一个呗

取消

感谢您的支持,我会继续努力的!

扫码支持
扫码支持
扫码打赏,你说多少就多少

打开支付宝扫一扫,即可进行扫码打赏哦