vue双向绑定的实现
Vue 双向绑定的实现原理
Vue 的双向绑定是通过数据劫持结合发布者-订阅者模式实现的,核心是 Object.defineProperty(Vue 2.x)或 Proxy(Vue 3.x)。
数据劫持
在 Vue 2.x 中,使用 Object.defineProperty 对对象的属性进行劫持,监听属性的读取和修改操作。
function defineReactive(obj, key, val) {
Object.defineProperty(obj, key, {
get() {
console.log(`读取 ${key}`);
return val;
},
set(newVal) {
if (newVal === val) return;
console.log(`设置 ${key} 为 ${newVal}`);
val = newVal;
}
});
}
依赖收集
每个被劫持的属性都有一个对应的 Dep 实例,用于收集依赖(Watcher)。当属性被读取时,当前的 Watcher 会被添加到 Dep 中。
class Dep {
constructor() {
this.subscribers = [];
}
depend() {
if (target && !this.subscribers.includes(target)) {
this.subscribers.push(target);
}
}
notify() {
this.subscribers.forEach(sub => sub());
}
}
观察者模式
Watcher 是一个观察者,当数据变化时,Dep 会通知所有 Watcher 更新视图。
let target = null;
class Watcher {
constructor(vm, key, cb) {
target = this;
this.vm = vm;
this.key = key;
this.cb = cb;
this.value = vm[key]; // 触发 getter,收集依赖
target = null;
}
update() {
this.value = this.vm[this.key];
this.cb.call(this.vm, this.value);
}
}
模板编译
Vue 的模板编译器会将模板中的指令(如 v-model)解析为对应的数据绑定和事件监听。
function compile(node, vm) {
if (node.nodeType === 1) {
const attrs = node.attributes;
for (let i = 0; i < attrs.length; i++) {
if (attrs[i].nodeName === 'v-model') {
const name = attrs[i].nodeValue;
node.addEventListener('input', e => {
vm[name] = e.target.value;
});
node.value = vm[name];
node.removeAttribute('v-model');
}
}
}
}
Vue 3 的 Proxy 实现
Vue 3 使用 Proxy 替代 Object.defineProperty,可以直接监听对象而非属性,支持数组变化检测。
function reactive(obj) {
return new Proxy(obj, {
get(target, key) {
console.log(`读取 ${key}`);
return target[key];
},
set(target, key, value) {
if (target[key] === value) return true;
console.log(`设置 ${key} 为 ${value}`);
target[key] = value;
return true;
}
});
}
双向绑定的实现步骤
- 数据劫持:通过
Object.defineProperty或Proxy监听数据变化。 - 依赖收集:在 getter 中收集依赖,setter 中通知更新。
- 模板编译:解析模板中的指令,建立数据与视图的关联。
- 观察者模式:Watcher 作为桥梁,连接数据变化和视图更新。
注意事项
- Vue 2.x 无法检测到对象属性的添加或删除,需使用
Vue.set或Vue.delete。 - Vue 3 的
Proxy可以监听整个对象,无需特殊处理动态属性。 - 双向绑定是
v-model的语法糖,本质是:value和@input的组合。







