vue 双向绑定实现
Vue 双向绑定的实现原理
Vue 的双向绑定主要通过数据劫持结合发布-订阅模式实现,核心是 Object.defineProperty(Vue 2.x)或 Proxy(Vue 3.x)。
数据劫持
Vue 2.x 使用 Object.defineProperty 劫持对象的属性访问和修改。通过定义属性的 getter 和 setter,在数据变化时触发更新。
let data = { name: 'Vue' };
Object.defineProperty(data, 'name', {
get() {
console.log('获取数据');
return value;
},
set(newValue) {
console.log('更新数据');
value = newValue;
// 触发视图更新
}
});
Vue 3.x 使用 Proxy 代理整个对象,可以监听动态添加的属性,性能更好。
let data = { name: 'Vue' };
let proxy = new Proxy(data, {
get(target, key) {
console.log('获取数据');
return target[key];
},
set(target, key, value) {
console.log('更新数据');
target[key] = value;
// 触发视图更新
return true;
}
});
依赖收集
每个响应式属性都有一个依赖收集器(Dep),用于存储依赖该属性的 Watcher 实例。当属性被访问时,当前 Watcher 会被添加到 Dep 中。
class Dep {
constructor() {
this.subs = [];
}
addSub(sub) {
this.subs.push(sub);
}
notify() {
this.subs.forEach(sub => sub.update());
}
}
发布-订阅模式
Watcher 是订阅者,当数据变化时,Dep 会通知所有 Watcher 更新视图。Watcher 在初始化时会触发属性的 getter,从而完成依赖收集。
class Watcher {
constructor(vm, key, cb) {
this.vm = vm;
this.key = key;
this.cb = cb;
Dep.target = this;
this.value = vm[key]; // 触发 getter
Dep.target = null;
}
update() {
const newValue = this.vm[this.key];
if (newValue !== this.value) {
this.value = newValue;
this.cb(newValue);
}
}
}
模板编译
Vue 的模板编译器会将模板中的指令(如 v-model)解析为渲染函数。对于 v-model,会生成对应的数据绑定和事件监听代码。
<input v-model="message">
编译后的代码类似于:
input.addEventListener('input', function(e) {
vm.message = e.target.value;
});
实现双向绑定的步骤
- 初始化时对数据进行响应式处理,劫持属性的
getter和setter。 - 解析模板,为每个指令创建对应的 Watcher,并在首次渲染时触发依赖收集。
- 当数据变化时,触发
setter,通知 Dep 调用所有 Watcher 的update方法更新视图。 - 对于
v-model,额外监听表单元素的输入事件,将用户输入同步到数据。
示例代码
以下是一个简化的双向绑定实现:
class Vue {
constructor(options) {
this.$data = options.data;
this.observe(this.$data);
this.compile(options.el);
}
observe(data) {
Object.keys(data).forEach(key => {
let value = data[key];
const dep = new Dep();
Object.defineProperty(data, key, {
get() {
if (Dep.target) dep.addSub(Dep.target);
return value;
},
set(newValue) {
if (newValue !== value) {
value = newValue;
dep.notify();
}
}
});
});
}
compile(el) {
const element = document.querySelector(el);
this.compileNode(element);
}
compileNode(element) {
element.childNodes.forEach(node => {
if (node.nodeType === 1) {
if (node.hasAttribute('v-model')) {
const key = node.getAttribute('v-model');
node.addEventListener('input', e => {
this.$data[key] = e.target.value;
});
new Watcher(this, key, value => {
node.value = value;
});
}
}
});
}
}
通过以上机制,Vue 实现了数据与视图的双向绑定,使得数据变化时视图自动更新,用户输入时数据自动同步。







