回顾上回提到,computed————计算属性的缓存与Watcher这个类的dirty属性有关,那么这次我们接着来看下,dirty属性到底取决于什么情况来变化,从而对computed进行缓存。依赖收集切入正题之前,我们先来看一个问题:如果一个computed的结果是受data属性下的值影响的,那么如何去捕获因某个值变化而引起的computed的变化?答案是:依赖收集根据上面的断点,在update函数执行之前,我们注意到,有个reactiveSetter函数在它之前。我们点击右侧调用栈中的reactiveSetter,此时有一个函数特别醒目:defineReactive$$1,经过又一次的断点,我们发现它在几处都有调用:在initRender函数中调用在walk函数中调用在实际断点调试的时候,我们很容易可以知道存在这样的,同时也是与本文有关的调用顺序(从下往上):defineReactive$$1walkObserverobserveinitDatainitState…Observer类根据上边提供的调用顺序,我们重点看一下几个关键的函数:observe/** * Attempt to create an observer instance for a value, * returns the new observer if successfully observed, * or the existing observer if the value already has one. /function observe (value, asRootData) { if (!isObject(value) || value instanceof VNode) { return } var ob; if (hasOwn(value, ‘ob’) && value.ob instanceof Observer) { ob = value.ob; } else if ( shouldObserve && !isServerRendering() && (Array.isArray(value) || isPlainObject(value)) && Object.isExtensible(value) && !value._isVue ) { ob = new Observer(value); } if (asRootData && ob) { ob.vmCount++; } return ob}光看注释我们都能知道,observe函数的作用是:为某个值创建一个observer实例,随后将这个observer实例返回,在这里起到一个对值进行筛选的作用Observer/* * Observer class that is attached to each observed * object. Once attached, the observer converts the target * object’s property keys into getter/setters that * collect dependencies and dispatch updates. /var Observer = function Observer (value) { this.value = value; this.dep = new Dep(); this.vmCount = 0; def(value, ‘ob’, this); if (Array.isArray(value)) { if (hasProto) { protoAugment(value, arrayMethods); } else { copyAugment(value, arrayMethods, arrayKeys); } this.observeArray(value); } else { this.walk(value); }};注释大意:每个被观察的对象都附属于Observer类。每次对对象的观察都会将它的 getter和setter属性覆盖,用以收集依赖以及触发更新walk && defineReactive$$1Observer.prototype.walk = function walk (obj) { var keys = Object.keys(obj); for (var i = 0; i < keys.length; i++) { defineReactive$$1(obj, keys[i]); }};/* * Define a reactive property on an Object. /function defineReactive$$1 ( obj, key, val, customSetter, shallow) { var dep = new Dep(); var property = Object.getOwnPropertyDescriptor(obj, key); if (property && property.configurable === false) { return } // cater for pre-defined getter/setters var getter = property && property.get; var setter = property && property.set; if ((!getter || setter) && arguments.length === 2) { val = obj[key]; } var childOb = !shallow && observe(val); Object.defineProperty(obj, key, { enumerable: true, configurable: true, get: function reactiveGetter () { var value = getter ? getter.call(obj) : val; if (Dep.target) { dep.depend(); if (childOb) { childOb.dep.depend(); if (Array.isArray(value)) { dependArray(value); } } } return value }, set: function reactiveSetter (newVal) { var value = getter ? getter.call(obj) : val; / eslint-disable no-self-compare / if (newVal === value || (newVal !== newVal && value !== value)) { return } / eslint-enable no-self-compare / if (process.env.NODE_ENV !== ‘production’ && customSetter) { customSetter(); } // #7981: for accessor properties without setter if (getter && !setter) { return } if (setter) { setter.call(obj, newVal); } else { val = newVal; } childOb = !shallow && observe(newVal); dep.notify(); } });}其中,这端代码是关键:get: function reactiveGetter () { var value = getter ? getter.call(obj) : val; if (Dep.target) { dep.depend(); if (childOb) { childOb.dep.depend(); if (Array.isArray(value)) { dependArray(value); } } } return value },如果阅读了整段defineReactive$$1函数,那么很容易就发现,dep不过是Dep类new出来的实例,那么即使不看Dep.prototype.depend的实现,你也知道dep.depend()其实也就是在收集依赖。 另外,这段代码意味着单单在data属性下声明一个变量是不会进行依赖收集的,需要变量在程序中被调用,那么才会被收集到依赖中(其实这也是一种优化)附Dep类下的相关实现/* * A dep is an observable that can have multiple * directives subscribing to it. */var Dep = function Dep () { this.id = uid++; this.subs = [];};Dep.prototype.addSub = function addSub (sub) { this.subs.push(sub);};Dep.prototype.removeSub = function removeSub (sub) { remove(this.subs, sub);};Dep.prototype.depend = function depend () { if (Dep.target) { Dep.target.addDep(this); }};Dep.prototype.notify = function notify () { // stabilize the subscriber list first var subs = this.subs.slice(); if (process.env.NODE_ENV !== ‘production’ && !config.async) { // subs aren’t sorted in scheduler if not running async // we need to sort them now to make sure they fire in correct // order subs.sort(function (a, b) { return a.id - b.id; }); } for (var i = 0, l = subs.length; i < l; i++) { subs[i].update(); }};总结上面说了这么多未免有点乱,最后重新梳理下computed实现缓存的思路:Vue在初始化data属性时,会将data属性下相关的变量进行观察(observe),同时重新设置它的getter和setter属性,以便在其被调用时收集到它的依赖。初始化computed调用computed时判断this.dirty属性,为true时调用evaluate重新计算它的值并将this.dirty置为false,将值存在this.value 上,再调用computed则直接返回this.value当computed中依赖的值发生变化时会自动触发该值的setter属性,紧接着调用notify函数,遍历一个subs数组,触发update函数将this.dirty重置为true当computed再次被调用时,由于this.dirty已经是true,则会重新计算