vue双向绑定原理及实现

Vue的双向绑定机制是其核心特性之一,它使得数据和视图能够自动保持同步。下面分别阐述Vue 2和Vue 3中双向绑定的原理,并给出手动实现的思路和示例。


一、Vue 2 双向绑定原理

Vue 2 的双向绑定基于 数据劫持 + 发布-订阅模式,具体由三个核心模块协作完成:

  • Observer:通过 Object.defineProperty 递归遍历数据对象的所有属性,将它们转换为 getter/setter。当属性被读取时(getter)收集依赖(Dep),当属性被修改时(setter)通知所有依赖(Watcher)进行更新。
  • Dep:依赖管理器,每个响应式属性都对应一个 Dep 实例,负责收集所有依赖于该属性的 Watcher,并在属性变化时通知它们。
  • Watcher:观察者,负责将模板中的表达式或指令与对应的数据属性建立联系。当数据变化时,Watcher 收到通知并触发视图更新(如重新渲染组件或更新DOM节点)。

v-model 的实现
v-model 是语法糖,在普通表单元素上,它本质上是绑定 value 属性并监听 input 事件。例如:

1
2
3
<input v-model="message">
<!-- 等价于 -->
<input :value="message" @input="message = $event.target.value">

在组件上使用 v-model,默认会传递一个名为 value 的 prop,并监听名为 input 的事件。

Vue 2 的局限性

  • 无法检测对象属性的添加或删除(需要使用 Vue.setvm.$set)。
  • 无法直接监听数组索引和长度的变化,因此 Vue 重写了数组的 7 个变异方法(push、pop 等)来实现响应式。

二、Vue 3 双向绑定原理

Vue 3 引入了 Proxy 作为新的响应式基础,解决了 Vue 2 中的诸多限制,同时提升了性能。

  • Proxy:直接代理整个对象,而不是对象的每个属性。它可以拦截对象的各种操作(get、set、deleteProperty、has 等),因此能够监听到属性的添加、删除,以及数组索引和长度的变化。
  • Reactive 模块:Vue 3 中使用 reactive 函数将数据转换为响应式对象,内部基于 Proxy 实现。同时提供了 ref 来处理基本类型值的响应式。
  • 依赖收集:在 Proxy 的 get 陷阱中进行依赖收集(使用 track 函数),在 set 陷阱中触发更新(使用 trigger 函数)。依赖追踪通过 effect 函数进行管理。

v-model 的变化

  • 默认 prop 名称由 value 改为 modelValue,默认事件由 input 改为 update:modelValue
  • 支持在同一个组件上绑定多个 v-model,例如 v-model:title="pageTitle",对应的 prop 为 title,事件为 update:title
  • 支持自定义修饰符,通过 prop 接收修饰符对象。

示例:

1
2
3
<ChildComponent v-model:title="pageTitle" />
<!-- 等价于 -->
<ChildComponent :title="pageTitle" @update:title="pageTitle = $event" />

三、如何手动实现简单的双向绑定

1. 基于 Object.defineProperty 的实现(类似 Vue 2)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
// 简易 Observer:将对象转换为响应式
function observe(obj) {
if (!obj || typeof obj !== 'object') return;
Object.keys(obj).forEach(key => {
let internalValue = obj[key];
const dep = new Dep(); // 每个属性一个依赖管理器

Object.defineProperty(obj, key, {
get() {
if (Dep.target) {
dep.depend(); // 收集依赖
}
return internalValue;
},
set(newVal) {
if (newVal === internalValue) return;
internalValue = newVal;
dep.notify(); // 通知更新
}
});
});
}

// 简易 Dep
class Dep {
constructor() {
this.subscribers = new Set();
}
depend() {
if (Dep.target) {
this.subscribers.add(Dep.target);
}
}
notify() {
this.subscribers.forEach(sub => sub.update());
}
}
Dep.target = null; // 当前正在计算的 Watcher

// 简易 Watcher
class Watcher {
constructor(updateFn) {
this.updateFn = updateFn;
this.get();
}
get() {
Dep.target = this;
this.updateFn(); // 执行时会触发属性的 get,从而完成依赖收集
Dep.target = null;
}
update() {
this.updateFn();
}
}

// 手动连接:将 input 元素与数据绑定
const data = { message: 'Hello' };
observe(data);

const input = document.getElementById('input');
const span = document.getElementById('span');

// 初始化显示
new Watcher(() => {
span.textContent = data.message;
});

// 监听 input 事件,更新数据
input.addEventListener('input', e => {
data.message = e.target.value;
});

2. 基于 Proxy 的实现(类似 Vue 3)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
function reactive(obj) {
return new Proxy(obj, {
get(target, key, receiver) {
const value = Reflect.get(target, key, receiver);
// 这里可以收集依赖(简化起见省略)
return value;
},
set(target, key, value, receiver) {
const oldValue = target[key];
const result = Reflect.set(target, key, value, receiver);
if (oldValue !== value) {
// 触发更新(简化:直接调用更新函数)
triggerUpdate();
}
return result;
}
});
}

// 使用
const data = reactive({ message: 'Hello' });
const input = document.getElementById('input');
const span = document.getElementById('span');

// 初始化
span.textContent = data.message;

input.addEventListener('input', e => {
data.message = e.target.value;
});

// 定义一个简单的更新函数
function triggerUpdate() {
span.textContent = data.message;
}

说明
上述代码省略了完善的依赖收集和调度,仅展示了核心的“数据变化 → 视图更新”和“视图输入 → 数据更新”的双向联动。在实际框架中,还需要处理模板编译、组件更新队列、异步批处理等复杂逻辑。


四、总结

  • Vue 2 使用 Object.defineProperty 实现响应式,结合发布-订阅模式,但存在无法监听属性添加/删除、数组索引变化等限制。
  • Vue 3 使用 Proxy 实现响应式,功能更强大,性能更好,且支持更多的数据操作拦截。
  • 手动实现双向绑定的核心是:数据劫持(监听数据变化)和 事件监听(监听用户输入),并通过回调或通知机制保持数据与视图的同步。

理解这些原理有助于更好地使用 Vue 框架,并能帮助我们在脱离框架的情况下实现自定义的双向绑定逻辑。


vue双向绑定原理及实现
https://cszy.top/20250315-vue双向绑定原理及实现/
作者
csorz
发布于
2025年3月15日
许可协议