一、Proxy的核心优势

Vue3选择Proxy作为响应式基础,相比Vue2的Object.defineProperty具有三大突破性改进:

  1. 全面拦截能力
    Proxy支持13种拦截操作(如get/set/deleteProperty/has等),可捕获对象增删改查等所有操作。而Vue2只能通过getter/setter拦截已有属性,无法检测新增/删除属性(需借助Vue.set/Vue.delete)。

  2. 按需代理机制
    Proxy仅在属性被访问时触发代理逻辑,避免Vue2初始化时递归遍历所有属性的性能损耗。例如嵌套对象{a: {b: 1}},只有访问a.b时才会生成子代理。

  3. 原生数组支持
    Proxy直接拦截数组的push/pop等方法,无需像Vue2那样重写数组原型方法。


二、响应式核心架构

Vue3的响应式系统由三大模块构成:

graph TD
    A[响应式对象] --> B(Proxy拦截)
    B --> C[依赖收集]
    B --> D[触发更新]
    C --> E[WeakMap存储依赖关系]
    D --> F[执行副作用函数]

1. 代理对象创建

通过reactive()函数创建响应式对象时,内部调用createReactiveObject()生成Proxy实例:

// 源码节选(简化)
function reactive(target) {
  return new Proxy(target, {
    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;
    }
  });
}

Proxy的handler对象定义了get/set等拦截器,结合Reflect实现元编程。


2. 依赖收集机制

当读取属性时,通过track()建立三级依赖存储结构:

// 依赖关系数据结构
type Target = Object;  // 原始对象
type Key = string;     // 属性名
type Dep = Set<ReactiveEffect>; // 副作用集合

const targetMap = new WeakMap<Target, Map<Key, Dep>>();
  • WeakMap:以原始对象为键,避免内存泄漏
  • Map:存储对象属性与依赖的映射
  • Set:存储同一属性的所有副作用函数

3. 副作用函数管理

通过effect()注册副作用函数:

let activeEffect: ReactiveEffect | null;

function effect(fn) {
  const effectFn = () => {
    cleanup(effectFn); // 清理旧依赖
    activeEffect = effectFn;
    fn();
    activeEffect = null;
  };
  effectFn.deps = [];
  effectFn();
}
  • 全局变量activeEffect标记当前运行的副作用
  • 每次执行前清理旧依赖,避免无效更新

三、响应式流程全解析

通过示例代码演示完整响应链:

const state = reactive({ count: 0 });

effect(() => {
  console.log('Count changed:', state.count);
});

state.count++; // 触发日志输出
  1. 初始化阶段
    reactive()创建Proxy代理对象,此时未触发任何拦截操作。

  2. 依赖收集阶段
    effect()执行副作用函数,读取state.count触发Proxy的get拦截:

    • 调用track(target, 'count'),将当前副作用存入targetMap
  3. 更新触发阶段
    修改state.count触发Proxy的set拦截:

    • 通过trigger(target, 'count')targetMap中取出依赖集合
    • 调度执行所有关联的副作用函数

四、进阶特性实现

1. 嵌套对象处理

Proxy自动递归代理嵌套对象:

const obj = reactive({ 
  nested: { value: 1 } 
});

// 访问nested时自动生成子代理
console.log(obj.nested.value); // 触发get拦截

2. 数组特殊处理

通过重写数组方法实现深度响应:

// 源码中的数组拦截器
const arrayInstrumentations = {
  includes(...args) {
    return arrMethods.includes.apply(toRaw(this), args);
  },
  // 其他方法重写...
}

3. 性能优化策略

  • 懒代理:仅在属性被访问时生成子代理
  • 缓存机制:通过proxyMap缓存已代理对象
  • 批量更新:通过微任务队列合并触发操作

五、与Vue2实现对比

特性 Vue3 (Proxy) Vue2 (defineProperty)
拦截范围 全属性(包括新增/删除) 仅限初始化时存在的属性
数组支持 原生方法直接拦截 需重写数组原型方法
性能表现 按需代理,内存占用更低 递归遍历初始化,内存开销大
嵌套对象处理 自动延迟代理 需要深度遍历初始化
代码复杂度 核心代码约300行(更简洁) 核心代码约800行(包含各种补丁)

六、实战注意事项

  1. 避免解构丢失响应性
    使用toRefs()保持解构后的响应性:

    const state = reactive({ count: 0 });
    const { count } = toRefs(state); // 保持响应
    
  2. 谨慎使用深度监听
    watch(obj, callback, { deep: true })可能引发性能问题,建议按需监听。

  3. Ref与Reactive选择

    • 基本类型用ref
    • 对象/数组用reactive
    • 需要传递引用时用ref包裹对象