vue双向数据绑定实现
Vue 双向数据绑定的实现原理
Vue 的双向数据绑定是通过结合数据劫持和发布-订阅模式实现的,核心依赖于 Object.defineProperty(Vue 2.x)或 Proxy(Vue 3.x)。以下是具体实现机制:
数据劫持(响应式系统)
通过 Object.defineProperty 或 Proxy 拦截对象的属性读写操作,实现数据变化的监听。

Vue 2.x 实现:
function defineReactive(obj, key, val) {
Object.defineProperty(obj, key, {
get() {
console.log(`读取 ${key}: ${val}`);
return val;
},
set(newVal) {
if (newVal !== val) {
console.log(`设置 ${key}: ${newVal}`);
val = newVal;
// 触发更新(通知依赖)
}
}
});
}
Vue 3.x 实现(Proxy):

function reactive(obj) {
return new Proxy(obj, {
get(target, key) {
console.log(`读取 ${key}`);
return Reflect.get(target, key);
},
set(target, key, newVal) {
if (newVal !== target[key]) {
console.log(`设置 ${key}: ${newVal}`);
Reflect.set(target, key, newVal);
// 触发更新
}
return true;
}
});
}
依赖收集与发布-订阅
- 依赖收集:在属性的
getter中,将当前正在执行的组件或计算属性(Watcher)添加到依赖列表。 - 通知更新:在属性的
setter中,通知所有依赖(Watcher)触发更新。
简化实现:
class Dep {
constructor() {
this.subscribers = new Set();
}
depend() {
if (activeWatcher) {
this.subscribers.add(activeWatcher);
}
}
notify() {
this.subscribers.forEach(watcher => watcher.update());
}
}
let activeWatcher = null;
class Watcher {
constructor(updateFn) {
this.updateFn = updateFn;
this.update();
}
update() {
activeWatcher = this;
this.updateFn();
activeWatcher = null;
}
}
模板编译与绑定
- 解析模板:将模板中的指令(如
v-model)解析为数据绑定和事件监听。 - 绑定事件:为表单元素(如
input)添加事件监听,将用户输入同步到数据层。
v-model 的等价展开:
<input v-model="message">
<!-- 等价于 -->
<input :value="message" @input="message = $event.target.value">
双向绑定的完整流程
- 数据劫持拦截对
data的访问和修改。 - 模板编译时,为每个绑定创建
Watcher,并在首次渲染时触发getter收集依赖。 - 用户输入触发事件,修改数据并触发
setter,通知所有依赖更新视图。
对比 Vue 2.x 与 Vue 3.x
| 特性 | Vue 2.x(Object.defineProperty) | Vue 3.x(Proxy) |
|---|---|---|
| 监听方式 | 递归遍历对象属性逐个劫持 | 直接代理整个对象 |
| 数组监听 | 需重写数组方法(如 push) |
原生支持数组变化 |
| 性能 | 初始化时递归性能较差 | 按需代理,性能更优 |
| 新属性响应 | 需手动调用 Vue.set |
自动响应新增属性 |
实现双向绑定的关键点
- 数据劫持的局限性:
Object.defineProperty无法检测到对象属性的添加或删除(Vue 2.x 需用Vue.set)。 - 异步更新队列:Vue 会将多次数据变更合并为一次视图更新,通过
nextTick实现。 - 虚拟 DOM 优化:通过 Diff 算法最小化 DOM 操作,提升性能。






