主题
监听器 watch 原理探索
Vue 中,watch
可以让我们更灵活的监听某个数据的变化来执行一些副作用
initWatch
我们在代码里面定义了watch
, 首先会在 initState
里面做初始化操作,源码如下:
ts
// src/core/instance/state.ts
initState(vm:Component) {
const opts = vm.$options
// ....
if (opts.watch && opts.watch !== nativeWatch) {
initWatch(vm, opts.watch)
}
}
若是传入的选项里面包含watch
属性,且opts.watch !== nativeWatch
就会进入initWatch
nativeWatch
是做了一个火狐浏览器的容错处理Firefox
在Object.prototype
上有一个“watch”功能
ts
// Firefox has a "watch" function on Object.prototype...
export const nativeWatch = {}.watch;
接着来看initWatch
ts
function initWatch(vm: Component, watch: Object) {
for (const key in watch) {
const handler = watch[key];
if (isArray(handler)) {
for (let i = 0; i < handler.length; i++) {
createWatcher(vm, key, handler[i]);
}
} else {
createWatcher(vm, key, handler);
}
}
}
initWatch
方法首先遍历watch
属性, 将每一个watch
取出来, 依次调用createWatcher
方法
这里还有一个细节点,watch
中的一个watch
对应的副作用函数可以为数组,在initWatch
中会将数组内每个副作用依次调用createWatcher
来创建watcher
js
export default {
props: {
msg: Number
},
data() {
return {
afterMag: 0
};
},
watch: {
// 数组类型的事件处理函数
msg:[
function add(v) {
console.log("add",v);
},
function (v) {
console.log("count", v);
},
function a(v) {
this.updateAfterMsg(v)
},
"fn"
],
// 对象类型的事件处理函数
afterMag: {
// immediate: true, 可选参数是否立即执行handler, 默认值false handler名字是固定的
handler(v, old) {
console.log(v, old);
}
}
},
methods: {
updateAfterMsg(val) {
this.afterMag += val;
}
}
};
因此关键的方法在于createWatcher
ts
function createWatcher(
vm: Component,
expOrFn: string | (() => any),
handler: any,
options?: Object
) {
if (isPlainObject(handler)) {
options = handler;
handler = handler.handler;
}
if (typeof handler === "string") {
handler = vm[handler];
}
return vm.$watch(expOrFn, handler, options);
}
createWatcher
内对传入的 hendler
进行兼容处理, handler
可以为 对象,字符串,函数 三种类型,得到真正的handler
后,最终都会调用并返回vm.$watch(expOrFn, handler, options);
, 其中expOrFn
为 watch 中的 key, handler 为真正的副作用函数
到这里,需要找到实例上的$watch
来源,同来自同一文件:
ts
// src/core/instance/state.ts
Vue.prototype.$watch = function (
expOrFn: string | (() => any),
cb: any,
options?: Record<string, any>
): Function {
const vm: Component = this;
// 若传入的cb 即watch[key] 为对象,则递归调用createWatcher
if (isPlainObject(cb)) {
return createWatcher(vm, expOrFn, cb, options);
}
options = options || {};
options.user = true;
// 创建watcher
const watcher = new Watcher(vm, expOrFn, cb, options);
if (options.immediate) {
// 若是立即执行
const info = `callback for immediate watcher "${watcher.expression}"`;
pushTarget();
// 调用时一并进行错误处理
invokeWithErrorHandling(cb, vm, [watcher.value], vm, info);
popTarget();
}
// 不是立即执行, 返回一个解绑函数
return function unwatchFn() {
watcher.teardown();
};
};
立即执行时候会执行
ts
export function invokeWithErrorHandling(
handler: Function,
context: any,
args: null | any[],
vm: any,
info: string
) {
let res;
try {
// 调用处理函数 handler
res = args ? handler.apply(context, args) : handler.call(context);
// 调用结果是promise但不是Vue实例时,进行错误的捕获
if (res && !res._isVue && isPromise(res) && !(res as any)._handled) {
res.catch((e) => handleError(e, vm, info + ` (Promise/async)`));
// issue #9511
// avoid catch triggering multiple times when nested calls
(res as any)._handled = true;
}
} catch (e: any) {
// 错误捕获
handleError(e, vm, info);
}
// 将调用的结果返回
return res;
}
到这里应该很明确了,watch 最终就是Watcher
的实现,要追踪 watch,就需要回到Watcher
中