1.监听属性的初始化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// 初始化watch
function initWatch(vm) {
let watch = vm.$options.watch;
for (let k in watch) {
const handler = watch[k]; //用户自定义watch的写法可能是数组 对象 函数 字符串
if (Array.isArray(handler)) {
// 如果是数组就遍历进行创建
handler.forEach((handle) => {
createWatcher(vm, k, handle);
});
} else {
createWatcher(vm, k, handler);
}
}
}
// 创建watcher的核心
function createWatcher(vm, exprOrFn, handler, options = {}) {
if (typeof handler === "object") {
options = handler; //保存用户传入的对象
handler = handler.handler; //这个代表真正用户传入的函数
}
if (typeof handler === "string") {
// 代表传入的是定义好的methods方法
handler = vm[handler];
}
// 调用vm.$watch创建用户watcher
return vm.$watch(exprOrFn, handler, options);
}

初始化watch时对options上的watch属性进行处理,如果是数组则遍历处理调用createWatcher函数,最后调用$watch创建用户watcher


2.$watch

1
2
3
4
5
6
7
8
9
10
import Watcher from "./observer/watcher";
Vue.prototype.$watch = function (exprOrFn, cb, options) {
const vm = this;
// user: true 这里表示是一个用户watcher
let watcher = new Watcher(vm, exprOrFn, cb, { ...options, user: true });
// 如果有immediate属性 代表需要立即执行回调
if (options.immediate) {
cb(); //如果立刻执行
}
};

创建自定义watch的核心方法,把用户定义的options和user:true传给构造函数watcher


3.Watcher改造

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
 this.user = options.user; //标识用户watcher
if (typeof exprOrFn === "function") {
this.getter = exprOrFn;
} else {
this.getter = function () {
//用户watcher传过来的可能是一个字符串 类似a.a.a.a.b
let path = exprOrFn.split(".");
let obj = vm;
for (let i = 0; i < path.length; i++) {
obj = obj[path[i]]; //vm.a.a.a.a.b
}
return obj;
};
}
// 实例化就进行一次取值操作 进行依赖收集过程
this.value = this.get();
}
run() {
const newVal = this.get(); //新值
const oldVal = this.value; //老值
this.value = newVal; //现在的新值将成为下一次变化的老值
if (this.user) {
// 如果两次的值不相同 或者值是引用类型 因为引用类型新老值是相等的 他们是指向同一引用地址
if (newVal !== oldVal || isObject(newVal)) {
this.cb.call(this.vm, newVal, oldVal);
}
} else {
// 渲染watcher
this.cb.call(this.vm);
}
}

为了兼容watch的写法,会将传入的字符串转成Vue实例对应的值,调用get方法保存一次旧值。
run方法中,当判断为用户watcher,那么执行用户传入的回调函数cb,并把新旧值传入。

4.小结

借助watcher实现。初始化响应式时,会调用createWatcher创建用户watcher,其中会将传入的handler存为watcher的cb函数,渲染更新时,会调用一次get获取旧值,执行用户watcher的run函数,判断为用户watcher时执行cb函数并将新值和旧值传入。