Vue 双向绑定原理剖析

Vue 双向绑定原理剖析(Vue 3 时代完整版,2026视角)

Vue 的“双向绑定”其实是两个方向的单向流动巧妙组合的结果:

  1. 数据 → 视图:响应式系统(Reactivity)自动更新 DOM(Vue 2 用 Object.defineProperty,Vue 3 用 Proxy + Reflect)
  2. 视图 → 数据:用户交互(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 2Vue 3优劣对比
响应式核心Object.definePropertyProxy + ReflectProxy 更全面、更高效(数组友好)
数组响应式需要 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 迁移、自定义修饰符),直接说,我继续拆解!

文章已创建 4298

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

相关文章

开始在上面输入您的搜索词,然后按回车进行搜索。按ESC取消。

返回顶部