结论
1、很多情况下,computed和watch可以实现相同的功能;
2、当需要在数据变化时执行异步或开销较大的操作时,使用watch会更好一些。因为computed会立即返回,此时异步操作可能还没有完成;
3、因为数据是响应式的,使得watch有意义。并不是因为watch了才使得数据是响应式的。
4、使用immediate:true,会在初始化watch时就立即执行handler回调函数,而不用等下一次数据更新。
5、使用deep:true,才会递归监听对象的属性(如果监听的是对象或数组)。
过程描述
在created函数调用之前,调用了initWatcher方法,(调用该方法时,若immediate为真,则会立即执行回调函数),为每一个watcher属性实例化了一个Watcher,new Watcher ,会传入监听的属性key、回调函数和options,包括handler、deep和immediate的值,实例化的结尾会调用watcher.prototype.get方法,该方法会获得值并返回,值存在watcher.value属性上。
Get函数(即autorun)的执行即导致了watcher被收集为依赖。至此成功的监听了属性。
Get时,如果deep为真,则会递归监听所有的属性。
更新描述
在数据发生变化时,调用watcher.prototype.update方法,最终会执行第三步的get。
watch的watcher中的lazy和sync都为false,所以会执行queueWatcher.
第一步
function initWatch (vm, watch) {
for (var key in watch) {
var handler = watch[key];
//此处省略一些细节
createWatcher(vm, key, handler);
}
}
第二步
Vue.prototype.$watch = function (
expOrFn,
cb,
options
) {
var vm = this;
if (isPlainObject(cb)) {
return createWatcher(vm, expOrFn, cb, options)
}
//watch属性的user设置为true
options.user = true;
var watcher = new Watcher(vm, expOrFn, cb, options);
if (options.immediate) {
//cb就是watch的回调函数,当immediate为true时,立即调用一次
cb.call(vm, watcher.value);
}
return function unwatchFn () {
watcher.teardown();
}
};
第三步 watch的get方法
Watcher.prototype.get = function get () {
//给全局变量Dep.target赋值
pushTarget(this);
value = this.getter.call(vm, vm);
//当deep为true时,会递归监听对象的所有属性
if (this.deep) {
traverse(value);
}
popTarget();
}
return value
};
第四步 更新数据
Watcher.prototype.update = function update () {
/* istanbul ignore else */
console.error('watch-update!!!');
if (this.lazy) {
//computed属性的watcher的lazy为true
console.log('lazy...');
this.dirty = true;
} else if (this.sync) {
// //如果this.sync为true,则直接运行this.run获取结果
console.log('sync...');
this.run();
} else {
////this.sync为false,调用queueWatcher()函数把所有要执行update()的watch push到队列中
console.log('other...');
queueWatcher(this);
}
};
第五步
Watcher.prototype.run = function run () {
if (this.active) {
var value = this.get();
if (
value !== this.value ||
//对于引用类型,值相同,可能指向的内存地址不同
isObject(value) ||
this.deep
) {
// set new value
var oldValue = this.value;
//其实执行的是 this.value=this.get();
this.value = value;
this.cb.call(this.vm, value, oldValue);
}
}
};
问答
Q:哪些对象是Watcher?
A :在源码中,看到三个地方会初始化Watcher对象。挂载组件(mountComponent方法)、初始化watch(initWatch方法)和初始化computed(initComputed方法)。
网友评论