vue watch 实现 原理
Vue 的 watch 实现原理
Vue 的 watch 功能用于监听数据变化并执行回调函数,其核心实现依赖于响应式系统和依赖收集机制。
初始化阶段
-
选项解析
在组件初始化时,Vue 会遍历watch选项,为每个监听属性创建一个Watcher实例。
例如:watch: { key: function(newVal, oldVal) { /* ... */ } } -
创建 Watcher
每个Watcher会记录监听的属性名(或路径)和回调函数,并标记为user watcher(用户自定义的侦听器)。
依赖收集
-
首次求值
Watcher在初始化时会主动触发一次求值,调用getter函数(即监听属性的读取逻辑)。
在读取过程中,触发属性的get拦截器,将当前Watcher添加到依赖列表(Dep 中)。 -
依赖关系建立
属性通过闭包引用一个Dep实例,用于存储所有依赖它的Watcher。
例如:
// 伪代码:响应式属性的 get 拦截 get() { Dep.target && dep.addSub(Dep.target); // 收集依赖 return value; }
触发更新
-
数据变更通知
当监听的属性被修改时,触发set拦截器,调用dep.notify()通知所有依赖的Watcher更新。 -
异步队列处理
Watcher的更新逻辑会被推入异步队列(通过nextTick实现),确保多次数据变更合并为单次回调执行。
例如:// 伪代码:响应式属性的 set 拦截 set(newVal) { value = newVal; dep.notify(); // 通知依赖更新 } -
回调执行
最终调用用户定义的回调函数,传入新值和旧值。如果是深度监听(deep: true),会递归遍历对象属性以触发依赖收集。
深度监听的实现
-
递归遍历
在getter中,如果启用deep选项,会递归访问对象的所有子属性,强制触发它们的get拦截器,从而收集深层依赖。
例如:function traverse(value) { if (isObject(value)) { Object.keys(value).forEach(key => traverse(value[key])); } } -
性能考虑
深度监听可能带来性能开销,因为需要递归遍历整个对象树。Vue 3 的watchAPI 通过 Proxy 优化了这一过程。
与计算属性的区别
-
懒执行 vs 主动执行
计算属性(computed)是懒执行的,仅在依赖变化时重新求值;而watch会主动执行回调。 -
返回值处理
计算属性需要返回一个值,而watch更适用于执行副作用(如异步操作)。
关键代码片段
Watcher 类核心逻辑
class Watcher {
constructor(vm, expOrFn, cb, options) {
this.vm = vm;
this.cb = cb;
this.deep = !!options.deep;
this.getter = parsePath(expOrFn); // 解析属性路径
this.value = this.get(); // 触发依赖收集
}
get() {
Dep.target = this;
let value = this.getter.call(this.vm, this.vm);
if (this.deep) traverse(value); // 深度监听
Dep.target = null;
return value;
}
update() {
const oldValue = this.value;
this.value = this.get();
this.cb.call(this.vm, this.value, oldValue); // 执行回调
}
}
响应式属性劫持
Object.defineProperty(obj, key, {
get() {
if (Dep.target) dep.addSub(Dep.target);
return value;
},
set(newVal) {
if (newVal === value) return;
value = newVal;
dep.notify();
}
});






