computed
首先Vue实例上会存在一个_computedWatchers
属性,然后以每个computed属性作为key值生成一个一个的watcher对象。
computed使用一般有两种形式,一个是返回直接返回一个计算函数,另一种是使用对象对象,定义一个get属性方法对应计算函数。总之拿到这个计算函数就行。
if (!isSSR) {
watchers[key] = new Watcher(
vm,
getter || noop,
noop,
computedWatcherOptions
);
}
服务端渲染就不考虑了,主要看Watcher构造函数的参数,当前Vue实例,getter就是前面的计算函数,第三个参数不需要传个空函数就行,computedWatcherOptions
对应{ lazy: true }
。
然后依次将computed属性,通过defineComputed
方法定义在实例上,依旧是借助Object.defineProperty
这个API。
当使用computed中的计算属性,进入定义好的getter方法。通过属性值,可以在前面说的_computedWatchers
中找到对应的watcher。
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
}
}
}
dirty
值在watcher初始化的时候通过lazy
赋值为true,调用watcher上的evaluate
方法,该方法就是调用watcher
的get
方法,然后结束后把dirty
变成false。当get
方法执行时,就会调用computed属性所对应watcher中的计算函数。联系平时使用computed,一般computed计算属性都是依赖其他数据计算出来的,因此在调用计算属性函数时,其他数据(比如data中定义的一些数据)的get方法也会被触发,这样当前watcher(computed的watcher)就会将其他数据对应的dep依赖收集起来。evaluate
执行完,此时Dep.target
已经变回了外层的watcher,再调用一次watcher.depend
方法将computed中watcher所依赖的dep储存进外层的watcher。这样即使data中的数据只在computed中被引用,相对应的dep也会存在于外层wacther中。
更新阶段,data中的值更新,父组件watcher执行update方法将更新操作推进异步队列,而通过lazy属性判断为computed属性对应的watcher,只需把dirty再次变回true,被引用时重复上面的步骤计算即可。
总结一下,computed每个属性都会单独创建一个watcher对象,然后计算属性函数依赖其他数据(一般是data中的),相应的使用这个函数时,触发其依赖数据的依赖收集,watcher.depend
将依赖添加到当前Vue实例的watcher
中。依赖数据变化时,只需将computed所对应watcher中的dirty
状态打开,使用时重新触发计算函数即可。
watch
和computed类似,watch
也是定义一个对象,循环watch对象,键值对应需要监听的值,属性值分为数组和非数组类型,createWatcher
处理watch的行为.通过整个方法也可以看到,watch监听函数的三种使用方式,如果是对象的话响应函数在handler
属性上;如果直接是字符就取实例中对应的methods方法;最后直接定义成一个函数使用。
function createWatcher (
vm,
expOrFn,
handler,
options
) {
if (isPlainObject(handler)) {
options = handler;
handler = handler.handler;
}
if (typeof handler === 'string') {
handler = vm[handler];
}
return vm.$watch(expOrFn, handler, options)
}
然后看实例上的$watch
方法
Vue.prototype.$watch = function (
expOrFn,
cb,
options
) {
var vm = this;
if (isPlainObject(cb)) {
return createWatcher(vm, expOrFn, cb, options)
}
options = options || {};
options.user = true;
var watcher = new Watcher(vm, expOrFn, cb, options);
if (options.immediate) {
try {
cb.call(vm, watcher.value);
} catch (error) {
handleError(error, vm, ("callback for immediate watcher \"" + (watcher.expression) + "\""));
}
}
return function unwatchFn () {
watcher.teardown();
}
}
实现核心就是这个方法,和computed也类似,主要实现还是Watcher构造函数,watcher实例放进vm._watchers
中。这里看不同的地方,配置user
为true,第二个参数expOrFn
这里是个字符对应要监听的数据,通过parsePath
返回一个获取该数据的函数,调用watcher上的get方法,数据所对应的dep触发依赖收集,就会把wacther存到dep.subs
中了,更新阶段和data中的数据行为基本相同,触发该dep中所有wacther进行update,最终执行run方法,通过user
属性再外面多包一层try catch防止报错终止程序。其他如果配置了immediate
为ture,会直接调用一次监听函数。最终返回一个函数,函数如果执行就是调用watcher的teardown
方法,作用将wacther实例从当前vm和数据依赖中移除,但是没发现具体使用了这个解绑方法的地方。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。