前言:
前言就是有了前几篇的基础对于vue响应式原理的初步了解之后,再去看这两个东西会方便很多。写这篇文章是为了一个梳理,还有一些其他的原因,年底再说。
先看computed
computed是在initState的时候,并且在初始化data之后,进行初始化的,来看看初始化的时候它干了什么:
function initComputed (vm, computed) { // $flow-disable-line var watchers = vm._computedWatchers = Object.create(null); // computed properties are just getters during SSR var isSSR = isServerRendering(); for (var key in computed) { var userDef = computed[key]; var getter = typeof userDef === 'function' ? userDef : userDef.get; if (getter == null) { warn( ("Getter is missing for computed property "" + key + ""."), vm ); } if (!isSSR) { // create internal watcher for the computed property. watchers[key] = new Watcher( vm, getter || noop, noop, computedWatcherOptions ); } // component-defined computed properties are already defined on the // component prototype. We only need to define computed properties defined // at instantiation here. if (!(key in vm)) { defineComputed(vm, key, userDef); } else { if (key in vm.$data) { warn(("The computed property "" + key + "" is already defined in data."), vm); } else if (vm.$options.props && key in vm.$options.props) { warn(("The computed property "" + key + "" is already defined as a prop."), vm); } } } }
两参数vm computed就是当前的vm组件和实例上的computed属性,说得通俗点就是new Vue的时候写的那个computed,然后我直接关注for in 里的代码:这里拿到computed上的key和对应的value之后,第一步是取得computed里key对应的value,这里value可能会是一个函数,一个对象,如果是函数就直接取,如果是对象就取他的get方法,然后会为当前的key生成一个watcher:
watchers[key] = new Watcher( vm, getter || noop, noop, computedWatcherOptions ); //computedWatcherOptions 等于 var computedWatcherOptions = { lazy: true };
这里啊,这个lazy如果你对watcher构造函数的源码模糊了,建议重新打开一个窗口看看,computed从源码角度来讲,为什么data里的值变了之后才会跟着变呢,是因为这个地方,
第二步是判断当前key是否与props或data里边的key重复,有则警告,没有就调用defineComputed:
function defineComputed ( target, key, userDef ) { var shouldCache = !isServerRendering(); 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 (sharedPropertyDefinition.set === noop) { sharedPropertyDefinition.set = function () { warn( ("Computed property "" + key + "" was assigned to but it has no setter."), this ); }; } Object.defineProperty(target, key, sharedPropertyDefinition); } //这里 var sharedPropertyDefinition = { enumerable: true, configurable: true, get: noop, set: noop };
直接看最后一句,vue的老套路了,劫持一个对象的属性的get set,还有一层作用是执行vm.key的时候把这个操作代理到了当前定义的get上,其实computed的set不怎么常用,那来看看这里的get是什么:shouldChache是isServerRendering的执行结果取反的结果,isServerRendering 字如其名自然跟服务端渲染相关,看源码的话,当在服务端运行的时候此值才有可能取真值,那这里他就是false,然后,shouldChache自然取true,接着走,然后computed对应的value一般为function,所以这里执行createComputedGetter:
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 } } }
这里实际是把key闭了一个包,返回的函数呢,就是sharedPropertyDefinition的get,所以,此后某个地方访问vm.key的时候这个闭包函数就会执行,再看一下这个函数执行的细节,第一步取得当前computed里key对应的watcher实例,然后判断watcher.dirty是否为true,这个dirty和上边提到的lazy在实例化watcher的时候用的是同一个值,在初始化所有computed的时候他们都为true,因此第一次渲染的时候,这个地方会执行到watcher.evaluate():
Watcher.prototype.evaluate = function evaluate () { this.value = this.get(); this.dirty = false; };
evaluate很简单,计算value,标记dirty为false,这里的get前面我们也应该讲过:他做了一件事情就是将当前watcher pushTarget,然后调用watcher实例的getter方法,getter方法就是初始化watcher的时候传进去的那个getter,那这个getter里的东西呢,我们注意到computed里一般都会访问到当前data的get方法,而前几篇讲了在get方法中,我们就能收集到当前的watcher,因此此时就会完成依赖收集。然后computed就定义完毕了。