Vue 3 如何用 Proxy 构建高效响应式数据?

178 阅读3分钟

Proxy 的核心机制

在 Vue 3 中,Proxy 主要用于 拦截对象的基本操作,包括 属性读取(get)、修改(set)、删除(deleteProperty) 等。这是 Vue 3 取代 Object.defineProperty 的根本原因。

Proxy 允许拦截的操作:

操作作用
get(target, key, receiver)读取属性时触发
set(target, key, value, receiver)设置属性时触发
deleteProperty(target, key)删除属性时触发
has(target, key)判断 key 是否存在(key in obj)
ownKeys(target)获取对象的所有键(Object.keys(obj))
apply(target, thisArg, args)代理函数调用
construct(target, args)代理 new 操作符

Proxy 的核心实现

让我们通过一个 手写 Proxy 劫持 的例子,模拟 Vue 3 响应式数据的基本实现:

const handler = {
  get(target, key) {
    console.log(`读取属性:${key}`);
    return Reflect.get(target, key);
  },
  set(target, key, value) {
    console.log(`修改属性:${key} -> ${value}`);
    return Reflect.set(target, key, value);
  }
};

const data = { message"Hello Vue 3" };
const proxyData = new Proxy(data, handler);

console.log(proxyData.message); // 读取属性:message
proxyData.message = "Updated"// 修改属性:message -> Updated

解读:

​ • Reflect.get(target, key):用于获取目标对象的值,等价于 target[key]。

​ • Reflect.set(target, key, value):用于修改目标对象的值,等价于 target[key] = value。

​ • Vue 3 就是基于 Proxy 设计 reactive() 来创建响应式数据。

Vue 3 如何基于 Proxy 构建响应式系统

Vue 3 的核心响应式 API reactive() 就是对 Proxy 的封装,具体实现如下:

function reactive(target) {
  if (typeof target !== "object" || target === null) {
    return target;
  }

  return new Proxy(target, {
    get(target, key, receiver) {
      console.log(`访问属性:${key}`);
      return Reflect.get(target, key, receiver);
    },
    set(target, key, value, receiver) {
      console.log(`修改属性:${key} -> ${value}`);
      return Reflect.set(target, key, value, receiver);
    }
  });
}

const state = reactive({ count0 });

console.log(state.count); // 访问属性:count
state.count++; // 修改属性:count -> 1

Vue 3 reactive() 实现的核心点

​ 1. 使用 Proxy 劫持整个对象,而不是每个属性

​ 2. 支持深层次代理(但 Vue 3 默认是惰性代理,访问时才创建嵌套对象的代理)。

​ 3. 拦截数组和对象的方法(如 push、pop、delete)

Vue 3 的依赖收集与副作用处理机制

Vue 3 采用 “响应式依赖收集 + 依赖触发” 来完成视图更新,核心组件包括:

​ • reactive():创建响应式数据(基于 Proxy)。

​ • effect():收集依赖,记录哪些属性被访问。

​ • trigger():数据变更时通知所有依赖执行。

1. Vue 3 依赖收集的核心实现

let activeEffect = null; // 当前正在执行的 effect
const targetMap = new WeakMap(); // 存储依赖

function effect(fn) {
  activeEffect = fn;
  fn(); // 立即执行一次,收集依赖
  activeEffect = null;
}

function track(target, key) {
  if (activeEffect) {
    let depsMap = targetMap.get(target);
    if (!depsMap) {
      depsMap = new Map();
      targetMap.set(target, depsMap);
    }
    let deps = depsMap.get(key);
    if (!deps) {
      deps = new Set();
      depsMap.set(key, deps);
    }
    deps.add(activeEffect);
  }
}

function trigger(target, key) {
  const depsMap = targetMap.get(target);
  if (!depsMap) return;
  const effects = depsMap.get(key);
  if (effects) {
    effects.forEach(fn => fn()); // 触发所有依赖
  }
}

// 结合 Proxy 创建 Vue 3 响应式对象
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 result = Reflect.set(target, key, value, receiver);
      trigger(target, key); // 触发更新
      return result;
    }
  });
}

2. Vue 3 响应式数据的完整示例

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

effect(() => {
  console.log(`count 更新:${state.count}`);
});

state.count++; // 触发更新:count 更新:1
state.count = 5; // 触发更新:count 更新:5

执行流程:

​ 1. effect(() => console.log(state.count)) 执行时,触发 get(),收集 state.count 的依赖。

​ 2. state.count++ 触发 set(),调用 trigger() 通知所有依赖执行 effect()。

Vue 3 Proxy 响应式系统的优化

1. 依赖按需收集

Vue 2 在初始化时遍历整个对象,而 Vue 3 只有在 访问属性时才进行代理,减少性能消耗。

2. 自动清理无效依赖

Vue 3 采用 WeakMap + Set 进行依赖存储,避免内存泄漏,Vue 2 需要手动管理依赖删除。

3. 只更新受影响的组件

Vue 3 的 trigger() 机制让每个组件只更新它所依赖的部分,避免 Vue 2 中全局重新计算的问题。

总结

Proxy 使 Vue 3 的响应式系统更高效,支持新增属性监听、数组操作拦截等。依赖追踪采用 WeakMap + Set 存储,提高性能,避免 Vue 2 的内存泄漏问题。 Vue 3 采用 Lazy Proxy(惰性代理),只有访问属性时才进行代理,减少初始化开销。Vue 3 的响应式机制通过effect() 进行自动依赖收集,让数据更新更智能、更高效。