官方定义类型:{ [key: string]: Function | { get: Function, set: Function } }详细:计算属性将被混入到 Vue 实例中。所有 getter 和 setter 的 this 上下文自动地绑定为 Vue 实例…计算属性的结果会被缓存,除非依赖的响应式属性变化才会重新计算。注意,如果某个依赖 (比如非响应式属性) 在该实例范畴之外,则计算属性是不会被更新的。上面这几段话其实可以归纳为以下几点:computed是计算属性,会被混入到Vue实例中computed的结果会被缓存,除非依赖的响应式属性变化才会重新计算如何初始化computed?同以往一样,先新建一个Vue项目,同时加入以下代码:export default { name: ’test’, data () { return { app: 666 } }, created () { console.log(‘app proxy –>’, this.appProxy) }, computed () { appProxy () { debugger return this.app } }}F12打开调试界面,刷新后断点停在了debugger的位置,同时可以看到右边的调用栈:appProxygetevaluatecomputedGettercreated…瞥到computedGetter之后,点进去,可以看到:function createComputedGetter (key) { return function computedGetter () { var watcher = this._computedWatchers && this._computedWatchers[key]; if (watcher) { if (watcher.dirty) { watcher.evaluate(); } if (Dep.target) { watcher.depend(); } return watcher.value } }}看到这里不禁一脸懵逼???? 当然,根据前面我们看源码的经验,没有思路时,直接搜索相关函数的调用位置,这里我们可以直接搜索createComputedGetter,看看它是在哪里调用的。此处忽略搜索的过程,直接给出我的结论:Vue中存在两种初始化computed的方法:在option中初始化在Vue.prototype.extend函数中初始化这两种初始化其实大同小异,我们选择在组件中写computed,自然断点就会跑到Vue.prototype.extend函数里:…if (Sub.options.computed) { initComputed$1(Sub);}…initComputed$1函数:function initComputed$1 (Comp) { // 拿到组件的computed var computed = Comp.options.computed; for (var key in computed) { // 循环遍历 defineComputed(Comp.prototype, key, computed[key]); }}显然,这句代码:defineComputed(Comp.prototype, key, computed[key])将computed挂载在了组件的原型上,下面来看下它的实现方式:defineComputed:function defineComputed ( target, key, userDef) { // 判断是否要将结果缓存下来 var shouldCache = !isServerRendering(); // 下面进行分类判断 // 对应的computed是函数的情况 if (typeof userDef === ‘function’) { sharedPropertyDefinition.get = shouldCache ? createComputedGetter(key) : createGetterInvoker(userDef); sharedPropertyDefinition.set = noop; } else { // 非函数的情况 sharedPropertyDefinition.get = userDef.get ? shouldCache && userDef.cache !== false ? createComputedGetter(key) : createGetterInvoker(userDef.get) : noop; sharedPropertyDefinition.set = userDef.set || noop; } if (process.env.NODE_ENV !== ‘production’ && sharedPropertyDefinition.set === noop) { sharedPropertyDefinition.set = function () { warn( (“Computed property "” + key + “" was assigned to but it has no setter.”), this ); }; } // 将sharedPropertyDefinition绑定到组件对象上 Object.defineProperty(target, key, sharedPropertyDefinition);}????感觉有点乱,最后再梳理下上边的逻辑:initComputed:执行initComputed,从Vue中拿到computed对象里所有的key值循环拿到的key值,调用defineComputed函数,把computed绑定到组件对象上defineComputed:判断是否在服务端渲染,是则computed的结果会被缓存,不是则不会缓存计算结果由于computed存在两种写法,这里也对函数跟对象的写法做了区分computed的结果缓存是如何实现的?上面我们大致梳理了下computed的初始化逻辑,现在我们回过头来再看一下官方定义,发现其中提到了计算属性会将计算结果缓存下来,那么这个计算结果到底是怎么被缓存下来的呢?回到defineComputeddefineComputed里最后将sharedPropertyDefinition绑定到组件对象上,在代码里面可以看到对sharedPropertyDefinition.get做了特殊处理,两种情况分别封装了:createComputedGettercreateGetterInvokercreateComputedGetter的实现:function createComputedGetter (key) { return function computedGetter () { var watcher = this._computedWatchers && this._computedWatchers[key]; if (watcher) { if (watcher.dirty) { watcher.evaluate(); } if (Dep.target) { watcher.depend(); } return watcher.value } }}createGetterInvoker的实现:function createGetterInvoker(fn) { return function computedGetter () { return fn.call(this, this) }}可以看到,服务端渲染确实是对计算属性的结果不做缓存的,但是我们对结果是如何缓存,依旧是一脸懵逼????回到最初的断点刷新页面回到一开始我们在appProxy中打下的断点,在调用栈中有两个显眼的函数:evaluateget分别点进去,我们可以看到:evaluate实现源码:Watcher.prototype.evaluate = function evaluate () { this.value = this.get(); this.dirty = false;};get实现源码:Watcher.prototype.get = function get () { pushTarget(this); var value; var vm = this.vm; try { value = this.getter.call(vm, vm); } catch (e) { if (this.user) { handleError(e, vm, (“getter for watcher "” + (this.expression) + “"”)); } else { throw e } } finally { // “touch” every property so they are all tracked as // dependencies for deep watching if (this.deep) { traverse(value); } popTarget(); this.cleanupDeps(); } return value};结合上面给出的createComputedGetter源码我们可以知道,computed的计算结果是通过Watcher.prototype.get来得到的,拿到value以后,在Wathcer.prototype.evaluate中执行了这样一行代码:…this.dirty = false;聪明的读者肯定猜到了,计算属性是否重新计算结果,肯定跟这个属性有关。接下来我们只要跟踪这个属性的变化,就可以轻松的知道计算属性的缓存原理了。