Vue 双向绑定原理剖析(Vue 3 时代完整版,2026视角)
Vue 的“双向绑定”其实是两个方向的单向流动巧妙组合的结果:
- 数据 → 视图:响应式系统(Reactivity)自动更新 DOM(Vue 2 用 Object.defineProperty,Vue 3 用 Proxy + Reflect)
- 视图 → 数据:用户交互(input、change 等事件)触发事件监听 → 更新数据
Vue 3 中 v-model 是语法糖,底层拆分成 value(或 modelValue) + onInput(或 update:modelValue)。
下面按层级从底层到表层完整拆解。
1. Vue 3 响应式系统核心(数据 → 视图方向)
Vue 3 放弃了 Object.defineProperty,转用 Proxy(性能更好,能劫持更多操作,如数组长度变化、in 操作、delete 等)。
核心 API:
reactive(obj)→ 返回 Proxy 代理对象ref(value)→ 返回 { value: xxx } 的 Ref 对象(内部也是 Proxy 或 getter/setter)computed()、watch()等依赖这个系统
Proxy 实现响应式的大致原理(简化版伪代码):
function reactive(raw) {
return new Proxy(raw, {
get(target, key, receiver) {
// 依赖收集:track
track(target, key); // 把当前 effect 收集到这个 key 的 deps 中
const result = Reflect.get(target, key, receiver);
// 如果是对象,继续代理(深度响应式)
if (isObject(result)) return reactive(result);
return result;
},
set(target, key, value, receiver) {
const oldValue = target[key];
const result = Reflect.set(target, key, value, receiver);
// 值真的变了才触发
if (oldValue !== value) {
trigger(target, key); // 通知所有依赖这个 key 的 effect 重新运行
}
return result;
},
// deleteProperty、has、ownKeys 等也需要处理
});
}
track / trigger 机制(依赖收集与派发更新):
- 使用 WeakMap>> 结构存储依赖关系
effect(fn)执行时会开启 activeEffect,get 时收集,set 时触发所有收集到的 effect
这就是为什么 ref.value++、reactiveObj.count++ 会自动更新视图的原因。
2. v-model 在普通 input 上的拆解(最基础形式)
<input v-model="message" />
编译后等价于:
<input
:value="message"
@input="message = $event.target.value"
/>
:value→ 数据 → 视图(响应式触发 set → trigger → 重新渲染 value)@input→ 视图 → 数据(用户输入 → 赋值给 message → 触发 set → 视图更新)
这就是最原始的双向绑定实现。
3. 组件上的 v-model(Vue 3.3+ 推荐方式:defineModel)
Vue 3.4 后 defineModel 正式成为宏(macro),极大简化写法。
子组件写法对比:
Vue 3.3 以前(手动):
// 子组件
defineProps(['modelValue']);
defineEmits(['update:modelValue']);
// 模板
<input :value="modelValue" @input="$emit('update:modelValue', $event.target.value)" />
Vue 3.4+(defineModel 宏,推荐):
// 子组件(一行搞定)
const model = defineModel(); // 返回一个 ref,默认绑定 modelValue + update:modelValue
// 或者带选项
const model = defineModel({ required: true, default: '' });
// 模板(和普通 ref 一样用)
<input v-model="model" />
defineModel 内部等价实现(官方简化版):
function defineModel(options) {
const props = useProps();
const emit = useEmit();
const local = ref(props.modelValue);
watch(() => props.modelValue, val => {
local.value = val; // 父 → 子 同步
}, { deep: true });
watch(local, val => {
emit('update:modelValue', val); // 子 → 父 通知
}, { deep: true });
return local;
}
一句话:defineModel 就是一个自动创建的 ref + 双向 watch + 事件桥接。
4. 自定义修饰符的 v-model(.trim / .number / .lazy)
Vue 3 支持在组件上自定义修饰符:
<MyInput v-model.trim="search" />
组件内可以通过 defineModel({ modifier: true }) 或手动处理。
5. 多 v-model(多个字段绑定,Vue 3 特性)
<UserForm v-model:name="user.name" v-model:age="user.age" />
子组件:
const name = defineModel('name');
const age = defineModel('age');
6. Vue 2 vs Vue 3 双向绑定对比(面试高频)
| 维度 | Vue 2 | Vue 3 | 优劣对比 |
|---|---|---|---|
| 响应式核心 | Object.defineProperty | Proxy + Reflect | Proxy 更全面、更高效(数组友好) |
| 数组响应式 | 需要 Vue.set / this.$set | 天然支持(Proxy 劫持 length 等) | Vue 3 完胜 |
| v-model 在组件上 | value + input(自定义 prop/event) | modelValue + update:modelValue | 更规范,defineModel 极大简化 |
| 性能 | 递归遍历所有属性 | 惰性代理 + 只代理已访问路径 | Vue 3 更好(尤其大对象) |
| 新增/删除属性 | 不响应式(需 Vue.set) | 响应式(Proxy set/has/delete) | Vue 3 完胜 |
7. 总结口诀(背下来面试稳)
- Vue 3 双向绑定 = Proxy 响应式(数据→视图) + 事件监听(视图→数据) + v-model 语法糖
- 普通标签:
:value + @input - 组件:
modelValue + update:modelValue(或defineModel一行实现) - 核心优势:Proxy 让数组、动态新增属性天然响应式,告别 Vue.set
- 2026 最佳实践:优先用 defineModel,配合
<script setup>,代码最简洁
如果你想看手写一个 mini-Vue 双向绑定(Proxy + 发布订阅 + 简单 diff),或者深挖 effect、track、trigger 的源码级细节,或者组件库中 v-model 的高级玩法(如 .sync 迁移、自定义修饰符),直接说,我继续拆解!