内容简介:在vue的开发中,我们不免会使用到计算属性,使用计算属性,vue会帮我们收集所有的该计算属性所依赖的所有data属性的依赖,当data属性改变时,便会重新获取computed属性,这样我们就不用关注计算属性所依赖的data属性的改变,而手动修改computed属性,这是vue强大之处之一。那么我们不免会产生疑问,computed属性为啥能随着data属性的改变而跟着改变的?带着这个疑问,我们来解析下vue的源码,看看它是如何实现computed的依赖收集。computed的依赖收集是借助vue的watche
在vue的开发中,我们不免会使用到计算属性,使用计算属性,vue会帮我们收集所有的该计算属性所依赖的所有data属性的依赖,当data属性改变时,便会重新获取computed属性,这样我们就不用关注计算属性所依赖的data属性的改变,而手动修改computed属性,这是vue强大之处之一。那么我们不免会产生疑问,computed属性为啥能随着data属性的改变而跟着改变的?带着这个疑问,我们来解析下vue的源码,看看它是如何实现computed的依赖收集。
整体流程
computed的依赖收集是借助vue的watcher来实现的,我们称之为computed watcher,每一个计算属性会对应一个computed watcher对象,该watcher对象包含了getter属性和get方法,getter属性就是计算属性对应的函数,get方法是用来更新计算属性(通过调用getter属性),并会把该computed watcher添加到计算属性依赖的所有data属性的订阅器列表中,这样当任何计算属性依赖的data属性改变的时候,就会调用该computed watcher的update方法,把该watcher标记为dirty,然后更新dom的dom watcher更新dom时,会触发dirty的computed watcher调用evaluate去计算最新的值,以便更新dom。
所以computed的实现是需要两个watcher来实现的,一个用来收集依赖,一个用来更新dom,并且两种watcher是有关联的。后续我们把更新DOM的watcher称为domWatcher,另一种叫computedWatcher。
initComputed
该方法是用来初始化computed属性的,它会遍历computed属性,然后做两件事:
1、为每个计算属性生成一个computedWathcer,后续计算属性依赖的data属性会把这个computedWatcher添加到自己订阅器列表中,以此来实现依赖收集。
2、挟持每个计算属性的get和set方法,set方法没有意义,主要是get方法,后面会提到。
function initComputed (vm, computed) { var watchers = vm._computedWatchers = Object.create(null); // 遍历所有的computed属性 for (var key in computed) { var userDef = computed[key]; // 每个计算属性对应的函数或者其get方法(computed属性可以设置get方法) var getter = typeof userDef === 'function' ? userDef : userDef.get; // .... if (!isSSR) { // 为每个计算属性生成一个Wathcer watchers[key] = new Watcher( vm, getter || noop, noop, computedWatcherOptions ); } if (!(key in vm)) { // defineComputed的作用就是挟持每个计算属性的get和set方法 defineComputed(vm, key, userDef); } else { // .... } } } 复制代码
defineComputed
如上面所述,definedComputed是挟持计算属性get和set方法,当然set方法对于计算属性是没什么作用,所以这里我们重点关注get方法,我们这里只需要知道get方法是触发依赖收集的关键,并且它把两种watcher进行了关联。
function defineComputed ( target, key, userDef ) { var shouldCache = !isServerRendering(); // 下面这段代码就是定义get和set方法了 if (typeof userDef === 'function') { sharedPropertyDefinition.get = shouldCache ? createComputedGetter(key) : userDef; sharedPropertyDefinition.set = noop; } else { sharedPropertyDefinition.get = userDef.get ? shouldCache && userDef.cache !== false ? createComputedGetter(key) : userDef.get : noop; sharedPropertyDefinition.set = userDef.set ? userDef.set : noop; } //... // 这里进行挟持 Object.defineProperty(target, key, sharedPropertyDefinition); } 复制代码
createComputedGetter
createComputedGetter有两个作用:
1、收集依赖 当domWatcher获取计算属性的时候,会触发该方法,然后computedWatcher会调用evaluate方法,最终会调用computedWatcher的get方法(下面会分析),来完成依赖的收集 2、关联两种watcher
通过第一步完成依赖收集后,computedWatcher能知道依赖的data属性的改变,从而计算出最新的计算属性值,那么它是怎么让另外一个watcher,即domWatcher知道的呢,其实就是通过调用computedWatcher.depend方法把两种watcher关联起来的,这个方法会把Dep.target(就是domWatcher)放入到计算属性依赖的所有data属性的订阅器列表中。
通过这两个作用,当计算属性依赖的data属性有改变的时候,就会调用domWatcher的update方法,它会获取计算属性的值,因此会触发computedGetter方法,使得computedWatcher调用evaluate来计算最新的值,以便domWatcher更新dom。
function createComputedGetter (key) { return function computedGetter () { // 取出initComputed创建的watcher var watcher = this._computedWatchers && this._computedWatchers[key]; if (watcher) { // 这个dirty的作用一个是避免重复计算,比如我们的模板中两次引用了这个计算属性,那么我们只需要计算一次就够了,一个是当计算属性依赖的data属性改变,会把这个计算属性对应的watcher给设置为dirty=true,然后 if (watcher.dirty) { // 这个会计算计算属性的值,并且会调用watcher的get方法,完成依赖收集 watcher.evaluate(); } // Dep.target指向的是模板中计算属性对应节点的domWatcher // 这个语句的意思就是把domWatcher放入到当前computedWatcher的所有依赖中,这样计算属性依赖的data值一改, // 就会触发domWatcher的update方法,它会获取计算属性的值从而触发这个computedGetter,然后computedWatcher会通过调用evaluate方法获取最新值, // 然后交给domWatcher更新到dom if (Dep.target) { watcher.depend(); // 关联了两种watcher } return watcher.value } } } 复制代码
接下来我们来分析下computedWatcher,看computed是如何借用computedWatcher来完成依赖收集的。
Computed Watcher
watcher是实现computed依赖的关键,它的第二个参数getter属性即是计算属性对应的方法或get方法。
var Watcher = function Watcher ( vm, expOrFn, cb, options, isRenderWatcher ) { this.vm = vm; // ... // watcher的第二个参数,即是我们计算属性对应的方法或get方法,用于算出计算属性的值 if (typeof expOrFn === 'function') { this.getter = expOrFn; } else { this.getter = parsePath(expOrFn); if (!this.getter) { this.getter = function () {}; } } // 不会立即计算 this.value = this.lazy ? undefined : this.get(); }; 复制代码
那么只要调用getter方法,那么它就会触发计算属性所有依赖的data的get方法,我们看下get方法
Object.defineProperty(obj, key, { enumerable: true, configurable: true, get: function reactiveGetter () { var value = getter ? getter.call(obj) : val; // Dep.target保存的是当前正在处理的Watcher,这里其实就是computedWatcher if (Dep.target) { // 这句代码其实就是将Dep.target放入到该data属性的订阅器列表当中 dep.depend(); //... } return value }, ... }) 复制代码
如上所述,其实就是把Dep.taget(当前的watcher)放入到该data属性的订阅器列表当中,那么这个时候,Dep.target指向哪个Watcher呢?我们看下watcher的get方法
Watcher.prototype.get = function get () { // 这句语句会把Dep.target执行本watcher pushTarget(this); var value; var vm = this.vm; try { // 调用getter,会触发上述讲的get,而get方法就会把Dep.target即本watcher放入到计算属性所依赖的data属性的订阅器列表中 //这样依赖的data属性有改变就会调用该watcher的update方法 value = this.getter.call(vm, vm); } catch (e) { //... } finally { //... popTarget(); // 将Dep.target指回上次的watcher,这里就是计算属性对应的domWatcher this.cleanupDeps(); } return value }; 复制代码
可以看到get方法开始运行时,把Dep.target指向计算属性对应的computedWatcher,然后调用watcher的getter方法,触发这个计算属性对应的data属性的get方法,就会把Dep.target指向的watcher加入到这些依赖的data的订阅器列表当中,以此完成依赖收集。
这样当我们的计算属性依赖的data属性改变的时候,就会调用订阅器的notify方法,它会遍历订阅器列表,其中就包含了该计算属性对应的computedWatcher和domWatcher,调用computedWatcher的update方法会把computedWatcher置为dirty,调用domWathcer的update方法会触发computedGetter,它会再次调用computedWatcher的evaluate计算出最新的值交给domWatcher去更新dom。
Watcher.prototype.update = function update () { if (this.lazy) { // computed专属的watcher走这里 this.dirty = true; } else if (this.sync) { // run方法会调用get方法,get方法会重新计算计算属性的值 // 但这个时候get方法不会再收集依赖了,vue会去重 this.run(); } else { queueWatcher(this); } }; Watcher.prototype.run = function run () { if (this.active) { // 调用get方法,重新计算计算属性的值 var value = this.get(); // 值改变了、Array或Object类型watch配置了deep属性为true的 if ( value !== this.value || isObject(value) || this.deep ) { var oldValue = this.value; this.value = value; if (this.user) { // watch 监听走此处 try { this.cb.call(this.vm, value, oldValue); } catch (e) { handleError(e, this.vm, ("callback for watcher \"" + (this.expression) + "\"")); } } else { // data数据改变,会触发更新函数cb,从而更新dom this.cb.call(this.vm, value, oldValue); } } } }; 复制代码
vuex实现
知道了computed是如何实现的就能知道vuex如果实现依赖收集了。我们看下vuex的源码
store._vm = new Vue({ data: { $$state: state }, computed // 这个computed就是我们定义的computed }) 复制代码
看到这段代码大家想到什么了吗,实际上是不是依赖于computed?只不过这里的computed收集的依赖是state,当state改变的时候就会调用computed对应watcher的update方法,从而更新computed的属性值并更新dom。这也是解释了为啥state依赖需要写在computed里面。
总结
- 遍历computed,为每个计算属性新建一个computedWatcher对象,并将该computedWatcher的getter属性赋值为计算属性对应的方法或者get方法。(大家应该知道计算属性不但可以是一个函数,还可以是一个包含get方法和set方法的对象吧)
- 使用Object.defineProperty挟持计算属性的get方法,当模版获取计算属性的值的时候,触发get方法,它会调用第一步创建的computedWatcher的evaluate方法,而evaluate方法就会调用watcher的get方法
- computedWatcher的get方法会将Dep.target指向该computedWatcher,并调用getter方法,getter方法会触发该计算属性依赖的所有data属性的get方法,从而把Dep.target指向的computedWatcher添加到data属性的订阅器列表中。同时,computedWatcher保存了依赖的data属性的订阅器(deps属性保存)。
- 同时调用computedWatcher的depend方法,它会把Dep.taget指向的domWatcher放入到计算属性依赖的data属性的订阅器列表中,如此计算属性依赖的data属性改变了,就会触发domWatcher和computedWatcher的update方法,computedWatcher赋值获取计算属性的最新值,domWatcher负责更新dom。
以上所述就是小编给大家介绍的《带你了解vue计算属性的实现原理以及vuex的实现原理》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:- Docker实现原理之 - OverlayFS实现原理
- 微热山丘,探索 IoC、AOP 实现原理(二) AOP 实现原理
- Docker原理之 - CGroup实现原理
- AOP如何实现及实现原理
- webpack 实现 HMR 及其实现原理
- 移动端下拉刷新头实现原理及代码实现
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。