vue 双向绑定的原理是什么
Vue 的双向绑定(Two-Way Data Binding)是其核心特性之一,主要通过 数据劫持(Data Observation) 和 发布-订阅模式(Pub-Sub) 实现。其核心原理可分为以下几个关键步骤:
一、Vue 2 的实现原理(基于 Object.defineProperty
)
1. 数据劫持(Data Observation)
- Vue 通过
Object.defineProperty
对数据对象的属性进行劫持,将其转换为 getter/setter。 - 当数据被读取时触发
getter
,当数据被修改时触发setter
。
// 简化版数据劫持实现
function defineReactive(obj, key, val) {
const dep = new Dep(); // 依赖管理器
Object.defineProperty(obj, key, {
get() {
if (Dep.target) { // 依赖收集(当前 Watcher)
dep.addSub(Dep.target);
}
return val;
},
set(newVal) {
if (newVal === val) return;
val = newVal;
dep.notify(); // 通知所有依赖更新
}
});
}
2. 依赖收集与派发更新(Dependency Tracking)
- 依赖收集:在模板编译时,每个数据属性会关联一个
Dep
(依赖管理器),当组件渲染时触发getter
,将当前的Watcher
(订阅者)添加到Dep
中。 - 派发更新:当数据变化时,触发
setter
,调用Dep.notify()
通知所有关联的Watcher
更新视图。
3. 虚拟 DOM 与视图更新
- 当数据变化触发
Watcher
更新时,Vue 会生成新的虚拟 DOM,通过 Diff 算法对比新旧虚拟 DOM,最终更新真实 DOM。
二、Vue 3 的实现原理(基于 Proxy
)
Vue 3 使用 Proxy
替代 Object.defineProperty
,解决了 Vue 2 的以下问题:
- 无法检测对象新增属性(需手动调用
Vue.set
)。 - 无法监听数组索引和长度的变化(需重写数组方法)。
- 性能优化:
Proxy
直接代理整个对象,无需递归遍历所有属性。
// 简化版 Proxy 实现
function reactive(obj) {
const handler = {
get(target, key, receiver) {
track(target, key); // 依赖收集
return Reflect.get(target, key, receiver);
},
set(target, key, value, receiver) {
const oldValue = target[key];
const result = Reflect.set(target, key, value, receiver);
if (oldValue !== value) {
trigger(target, key); // 派发更新
}
return result;
}
};
return new Proxy(obj, handler);
}
三、双向绑定的具体实现(v-model
为例)
v-model
是语法糖,本质上是 :value
+ @input
的封装:
<input v-model="message">
<!-- 等价于 -->
<input :value="message" @input="message = $event.target.value">
实现步骤:
- 数据劫持:将
message
属性设置为响应式。 - 模板编译:将
v-model
解析为value
属性和input
事件。 - 事件监听:当输入框内容变化时,通过
input
事件更新message
的值。 - 视图更新:
message
变化触发响应式系统,更新所有依赖该数据的视图。
四、Vue 2 与 Vue 3 的对比
特性 | Vue 2(Object.defineProperty) | Vue 3(Proxy) |
---|---|---|
对象监听 | 需递归遍历所有属性 | 直接代理整个对象 |
数组监听 | 需重写数组方法(如 push 、pop ) | 直接监听索引和长度变化 |
新增属性支持 | 需 Vue.set | 自动支持 |
性能 | 初始化时递归遍历属性,性能较差 | 惰性劫持,性能更优 |
五、手写简化版双向绑定
<input id="input">
<div id="display"></div>
<script>
const data = { text: '' };
const input = document.getElementById('input');
const display = document.getElementById('display');
// 数据劫持
const reactiveData = new Proxy(data, {
set(target, key, value) {
target[key] = value;
display.textContent = value; // 更新视图
return true;
}
});
// 视图 → 数据
input.addEventListener('input', (e) => {
reactiveData.text = e.target.value;
});
// 数据 → 视图(初始化)
reactiveData.text = 'Hello Vue!';
</script>
总结
- Vue 2:基于
Object.defineProperty
实现数据劫持,通过Dep
和Watcher
管理依赖。 - Vue 3:使用
Proxy
实现更高效、全面的数据劫持,支持动态新增属性和数组索引监听。 v-model
:本质是:value
+@input
的语法糖,通过响应式系统实现数据与视图的双向同步。