实现Vue双向数据绑定
Vue双向数据绑定的实现原理
Vue的双向数据绑定主要通过数据劫持结合发布者-订阅者模式实现,核心是利用Object.defineProperty()或Proxy来监听数据变化。
核心实现步骤
数据劫持
通过Object.defineProperty()递归遍历数据对象的属性,将其转换为getter/setter。当数据被访问或修改时触发相应的钩子函数。
function defineReactive(obj, key, val) {
Object.defineProperty(obj, key, {
get() {
return val;
},
set(newVal) {
if (newVal === val) return;
val = newVal;
dep.notify(); // 通知所有订阅者
}
});
}
依赖收集
每个被劫持的属性都会拥有一个Dep实例,用于收集依赖(Watcher实例)。当属性被访问时,当前Watcher会被添加到Dep中。
class Dep {
constructor() {
this.subs = [];
}
addSub(sub) {
this.subs.push(sub);
}
notify() {
this.subs.forEach(sub => sub.update());
}
}
Watcher Watcher作为观察者,在初始化时会将自身赋值给全局变量,然后触发属性的getter进行依赖收集。当数据变化时,Dep会通知所有Watcher调用update方法更新视图。
class Watcher {
constructor(vm, exp, cb) {
this.vm = vm;
this.exp = exp;
this.cb = cb;
this.value = this.get(); // 触发依赖收集
}
update() {
this.run();
}
run() {
const value = this.vm.data[this.exp];
if (value !== this.value) {
this.value = value;
this.cb.call(this.vm, value);
}
}
}
完整流程示例
- 初始化Vue实例时,对data进行递归遍历,使用
Object.defineProperty设置getter/setter - 编译模板时,为每个指令/插值表达式创建Watcher实例
- Watcher在初始化时会读取对应数据,触发getter将自身添加到Dep中
- 当数据变化时,setter被触发,调用Dep.notify()通知所有Watcher更新视图
现代Vue的改进
Vue 3.0使用Proxy替代Object.defineProperty,优势在于:
- 直接监听对象而非属性
- 可监听数组变化
- 无需递归遍历初始化
function reactive(obj) {
return new Proxy(obj, {
get(target, key) {
track(target, key); // 依赖收集
return target[key];
},
set(target, key, value) {
target[key] = value;
trigger(target, key); // 触发更新
return true;
}
});
}
双向绑定的模板实现
对于v-model指令,本质是语法糖:
<input v-model="message">
等价于:
<input
:value="message"
@input="message = $event.target.value"
>






