vue底层实现双向绑定
Vue 双向绑定的底层实现原理
Vue 的双向绑定核心是通过数据劫持结合发布-订阅模式实现的,主要依赖以下技术点:
- Object.defineProperty(Vue 2.x)或 Proxy(Vue 3.x)
- 依赖收集与追踪
- 虚拟 DOM 差异更新
数据劫持的实现
在 Vue 2.x 中,通过 Object.defineProperty 劫持对象的属性访问和修改:
function defineReactive(obj, key, val) {
Object.defineProperty(obj, key, {
get() {
console.log('get', key);
return val;
},
set(newVal) {
if (newVal !== val) {
console.log('set', key);
val = newVal;
// 触发更新
}
}
});
}
在 Vue 3.x 中改用 Proxy,直接代理整个对象:
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); // 触发更新
}
});
}
依赖收集与派发更新
-
Watcher(观察者)
每个组件实例对应一个 Watcher,在渲染过程中会触发数据的getter,将 Watcher 添加到依赖列表中。 -
Dep(依赖管理器)
每个响应式属性都有一个 Dep 实例,用于存储所有依赖该属性的 Watcher。
class Dep {
constructor() {
this.subscribers = new Set();
}
depend() {
if (activeWatcher) {
this.subscribers.add(activeWatcher);
}
}
notify() {
this.subscribers.forEach(watcher => watcher.update());
}
}
虚拟 DOM 与更新优化
-
初次渲染
通过render函数生成虚拟 DOM,再转为真实 DOM。 -
数据变更时
触发 Watcher 的update,重新生成虚拟 DOM,通过diff算法比对差异后局部更新。
// 简化的 diff 示例
function patch(oldVnode, newVnode) {
if (oldVnode.tag !== newVnode.tag) {
replaceNode(oldVnode, newVnode);
} else {
updateProps(oldVnode, newVnode);
patchChildren(oldVnode, newVnode);
}
}
双向绑定的完整流程
-
模板编译
v-model等指令被编译为value属性和input事件监听。 -
数据响应化
初始化时对数据进行劫持,建立 Dep-Watcher 关联。 -
视图更新
数据变化时通过虚拟 DOM 高效更新界面,用户输入通过事件触发数据修改。
与单向数据流的区别
虽然 Vue 通过 v-model 提供了双向绑定的语法糖,但底层仍是单向数据流:
- 数据 -> 视图:通过响应式系统自动更新
- 视图 -> 数据:需要显式的事件监听(如
@input)







