7.15 watch
到这里,关于响应式系统的分析大部分内容已经分析完毕,我们上一节还遗留着一个问题,Vue
对用户手动添加的watch
如何进行数据拦截。我们先看看两种基本的使用形式。
// watch选项
var vm = new Vue({
el: '#app',
data() {
return {
num: 12
}
},
watch: {
num() {}
}
})
vm.num = 111
// $watch api方式
vm.$watch('num', function() {}, {
deep: ,
immediate: ,
})
7.15.1 依赖收集
我们以watch
选项的方式来分析watch
的细节,同样从初始化说起,初始化数据会执行initWatch
,initWatch
的核心是createWatcher
。
function initWatch (vm, watch) {
for (var key in watch) {
var handler = watch[key];
// handler可以是数组的形式,执行多个回调
if (Array.isArray(handler)) {
for (var i = 0; i < handler.length; i++) {
createWatcher(vm, key, handler[i]);
}
} else {
createWatcher(vm, key, handler);
}
}
}
function createWatcher (vm,expOrFn,handler,options) {
// 针对watch是对象的形式,此时回调回选项中的handler
if (isPlainObject(handler)) {
options = handler;
handler = handler.handler;
}
if (typeof handler === 'string') {
handler = vm[handler];
}
return vm.$watch(expOrFn, handler, options)
}
无论是选项的形式,还是api
的形式,最终都会调用实例的$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);
// 当watch有immediate选项时,立即执行cb方法,即不需要等待属性变化,立刻执行回调。
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();
}
};
}
$watch
的核心是创建一个user watcher
,options.user
是当前用户定义watcher
的标志。如果有immediate
属性,则立即执行回调函数。而实例化watcher
时会执行一次getter
求值,这时,user watcher
会作为依赖被数据所收集。这个过程可以参考data
的分析。
var Watcher = function Watcher() {
···
this.value = this.lazy
? undefined
: this.get();
}
Watcher.prototype.get = function get() {
···
try {
// getter回调函数,触发依赖收集
value = this.getter.call(vm, vm);
}
}
7.15.2 派发更新
watch
派发更新的过程很好理解,数据发生改变时,setter
拦截对依赖进行更新,而此前user watcher
已经被当成依赖收集了。这个时候依赖的更新就是回调函数的执行。