API用法
/**
* watch
* @params expOrFn {Strng | Function} 监听项
* @params callback {Function | Object} 回调函数
* @params options ? { 配置项
* {boolean} deep 深度监听
* {boolean} immediate 立即执行
* @returns {Function} unwatch 此函数调用后会取消该监听
*/
vm.$watch(expOrFn, callback, [options])
实现思路
- 通过实例化一个
Watcher
对象来实现基本的功能:监听项变化时执行回调函数。(该部分细节点击此处:Vue中对象的响应式原理解析) - 处理监听项为函数的情况(在
Watcher
中) - 返回一个取消监听的函数(在
Watcher
中:watcher.teardown()
) - 处理
options.immediate: true
的情况:直接执行一遍callback
- 处理
options.deep: true
的情况:遍历监听项的所有子项,触发子项的收集依赖逻辑,将该callback
放入遍历的所有子项中。
内部原理
当expOrFn是函数时,它不仅仅可以动态返回数据,其中读取的所有数据也都会被观察。
vm.$watch:
Vue.prototype.$watch = function (expOrFn, cb, options) {
const vm = this;
options = options || {};
const tacher = new Watcher(vm, expOrFn, cb, options);
// 立即执行
if (options.immediate) {
cb.call(vm, watcher.value);
}
return function unwatchFn() {
watcher.teardown();
}
}
Watcher:
class Watcher {
constructor(vm, expOrFn, cb, options) {
this.vm = vm;
this.deep = (options) ? !!options.deep : false;
this.deps = []; // 用于存储Dep,列表项表示这个Watcher依赖都被谁订阅了
this.depIds = new Set(); // 用于存储Dep的ID,来防止重复添加
if (typeof expOrFn === 'function') {
this.getter = expOrFn;
} else {
this.getter = parsePath(expOrFn); // 解析简单路径的值
}
this.cb = cb;
this.value = this.get();
}
/**
* 增加Dep
* @params dep Object Dep
*/
addDep(dep) {
const id = dep.id;
// 防止重复增加Dep
if (!this.depIds.has(id)) {
this.depIds.add(id);
this.deps.push(dep);
dep.addSub(this);
}
}
get() {
window.target = this;
let value = this.getter.call(this.vm, this.vm);
this.deep && traverse(value); // 深度监听的处理,原理是将value所有的子集收集当前的Walker
window.target = undefined;
return value;
}
update() {
const oldValue = this.value;
this.value = this.get();
this.cb.call(this.vm, this.value, oldValue); // 核心操作
}
/**
* 从所有依赖项的Dep列表中将自己移除
*/
teardown() {
let i = this.deps.length;
while (i--) {
this.deps[i].removeSub(this);
}
}
}
function parsePath(path) {
if (bailRE.test(path)) return; // 过滤掉特殊字符
const segments = path.split('.');
return function (obj) {
for (let i = 0, len = segments.length; i < len; i++) {
if (!obj) return;
obj = obj[segments[i]];
}
return obj;
}
}
teardown()
的实现思路:
在该Watcher
实例被添加到对象Dep
的subs
中时,将Dep
存到Watcher
的this.deps
中,teardown()
方法会遍历this.deps
中的每一个Dep
,调用该Dep
对象上的removeSub()
方法,将该Watcher
实例在Dep
对象的subs
列表中去除,这样Dep
对象发生变化后就不会通知到该Watcher
了。
这里非常强调的一点:
一定要在window.target = undefined
之前去触发子值的收集依赖逻辑,这样才能保证子集收集的依赖是当前这个Watcher
。如果在window.target = undefined
之后去触发收集依赖的逻辑,那么其实当前的Watcher
并不会被收集到子值的以来列表中,也就无法实现deep
的功能。
Dep
class Dep {
constructor() {
this.id = uid++;
this.subs = [];
}
depend() {
if (window.target) {
// this.addSub(window.target); // 废弃
window.target.addDep(this); // 新增
}
}
removeSub(sub) {
const index = this.subs.indexOf(sub);
if(index > -1) {
return this.subs.splice(index, 1);
}
}
// ... 详情可了解本专栏的其他文章
}