深入理解 Vue3 数据绑定实现原理

深入理解 Vue3 数据绑定实现原理 🔗

本文从响应式系统的核心出发,剖析 Vue3 如何基于 Proxy 实现数据绑定,对比 Vue2 的 Object.defineProperty,并详解 ref、reactive、effect 等 API 的底层机制,帮助读者建立完整的响应式心智模型。

前言

Vue3 的响应式系统相比 Vue2 进行了全面重构,从 Object.defineProperty 升级为 Proxy,带来了更好的性能与更完善的功能。理解其数据绑定原理,不仅能帮助我们写出更高效的 Vue 代码,也是深入前端框架设计的必经之路。本文将带你从零理解 Vue3 响应式的实现思路。

一、Vue2 与 Vue3 响应式对比

1.1 Vue2:Object.defineProperty 的局限

Vue2 使用 Object.defineProperty 劫持对象的 getter/setter:

javascript 复制代码
function defineReactive(obj, key, val) {
  Object.defineProperty(obj, key, {
    get() {
      console.log('get', key);
      return val;
    },
    set(newVal) {
      if (newVal !== val) {
        val = newVal;
        console.log('set', key);
      }
    }
  });
}

主要局限

  • 无法监听数组索引和 length 变化,需对数组方法做 hack
  • 无法监听对象属性的新增和删除
  • 必须递归遍历对象,初始化成本高

1.2 Vue3:Proxy 的优势

Vue3 使用 Proxy 代理整个对象,可拦截 13 种操作:

javascript 复制代码
const obj = { a: 1 };
const proxy = new Proxy(obj, {
  get(target, key) {
    console.log('get', key);
    return Reflect.get(target, key);
  },
  set(target, key, value) {
    console.log('set', key);
    return Reflect.set(target, key, value);
  }
});
proxy.a;      // get a
proxy.b = 2;  // set b,可监听新增属性

优势:支持数组、支持新增/删除属性、性能更好、代码更简洁。

二、reactive:基于 Proxy 的响应式对象

Vue3 的 reactive() 返回一个 Proxy 包装的对象:

javascript 复制代码
function reactive(target) {
  return new Proxy(target, {
    get(target, key, receiver) {
      const res = Reflect.get(target, key, receiver);
      track(target, key);  // 依赖收集
      if (typeof res === 'object' && res !== null) {
        return reactive(res);  // 懒代理:访问时才递归
      }
      return res;
    },
    set(target, key, value, receiver) {
      const res = Reflect.set(target, key, value, receiver);
      trigger(target, key);  // 触发更新
      return res;
    }
  });
}

懒代理:只有访问到的嵌套对象才会被代理,避免一次性递归整个对象,提升性能。

三、ref:基本类型的响应式包装

ref 用于包装基本类型(number、string、boolean 等),因为 Proxy 只能代理对象:

javascript 复制代码
function ref(value) {
  const refObj = {
    get value() {
      track(refObj, 'value');
      return value;
    },
    set value(newVal) {
      if (newVal !== value) {
        value = newVal;
        trigger(refObj, 'value');
      }
    }
  };
  return refObj;
}

在模板中使用 ref 时会自动解包,无需 .value;在 <script setup> 中需要 .value 访问。

四、依赖收集与派发更新

4.1 核心数据结构

javascript 复制代码
const targetMap = new WeakMap();  // target -> key -> Set<effect>

function track(target, key) {
  let depsMap = targetMap.get(target);
  if (!depsMap) targetMap.set(target, (depsMap = new Map()));
  let dep = depsMap.get(key);
  if (!dep) depsMap.set(key, (dep = new Set()));
  dep.add(activeEffect);  // 当前正在执行的 effect
}

function trigger(target, key) {
  const depsMap = targetMap.get(target);
  if (!depsMap) return;
  const dep = depsMap.get(key);
  dep?.forEach(effect => effect());
}

4.2 effect:副作用函数

effect 会在依赖的响应式数据变化时重新执行:

javascript 复制代码
let activeEffect;

function effect(fn) {
  activeEffect = fn;
  fn();
  activeEffect = null;
}

// 使用
const state = reactive({ count: 0 });
effect(() => {
  console.log(state.count);  // 自动收集 count 的依赖
});
state.count++;  // 触发 effect 重新执行

五、computed 与 watch 的简化实现

5.1 computed

computed 本质是带缓存的 effect:

javascript 复制代码
function computed(getter) {
  let value;
  let dirty = true;
  const effectFn = effect(getter, {
    lazy: true,
    scheduler() {
      dirty = true;
      trigger(obj, 'value');
    }
  });
  const obj = {
    get value() {
      if (dirty) {
        value = effectFn();
        dirty = false;
      }
      track(obj, 'value');
      return value;
    }
  };
  return obj;
}

5.2 watch

watch 基于 effect + 深度比较,当源变化时执行回调。

六、总结

特性 Vue2 Vue3
实现方式 Object.defineProperty Proxy
数组监听 需 hack 方法 原生支持
新增/删除属性 需 Vue.set 原生支持
嵌套代理 初始化时递归 懒代理,按需
性能 一般 更优

Vue3 的响应式系统通过 Proxy + 依赖收集(track/trigger)+ 副作用(effect) 三件套,实现了高效、完整的数据绑定。理解这套机制,有助于我们正确使用 reactiverefcomputed,并避免常见的响应式陷阱(如解构丢失响应性)。

参考资料

相关推荐
行走的陀螺仪1 小时前
前端公共库开发保姆级路线:从0到1复刻VueUse官方级架构(pnpm+Turbo+VitePress)
前端·架构
前端付豪1 小时前
组件拆分重构 App.vue
前端·架构·代码规范
Wect1 小时前
React 更新触发原理详解
前端·react.js·面试
cxxcode1 小时前
Web 帧渲染与 DOM 准备
前端
光影少年1 小时前
React Hooks的理解?常用的有哪些?
前端·react.js·掘金·金石计划
大鸡爪1 小时前
Vue3 组件库实战(七):从本地到 NPM:版本管理与自动化发布指南(下)
前端·vue.js
幸福摩天轮1 小时前
记录commonjs的一道面试题
前端
qq_406176141 小时前
详解Vue中的计算属性(computed)和观察属性(watch)
开发语言·前端·javascript·vue.js·前端框架
kyriewen1 小时前
Grid 网格布局:二维世界的布局王者,像下围棋一样掌控页面
前端·css·html