Vue 3的响应式系统是其核心特性之一,相比Vue 2的Object.defineProperty实现,Vue 3采用ES6的Proxy API重构了响应式机制,带来了更强大的功能和更好的性能。本文将通过实战代码,从底层原理到实际应用,解析Proxy如何实现数据响应式,以及Vue 3响应式系统的核心工作流程。

一、从Object.defineProperty到Proxy的进化

Vue 2的响应式实现依赖Object.defineProperty,通过遍历对象属性并为其设置getter和setter来追踪变化。但这种方式存在固有的局限性:

  • 无法监听对象新增属性或删除属性
  • 无法监听数组索引和长度变化
  • 需要递归遍历对象,性能开销大

Vue 3改用Proxy解决了这些问题。Proxy可以创建一个对象的代理,拦截并自定义对象的基本操作(如属性访问、赋值、删除等),具有以下优势:

  • 原生支持监听对象新增/删除属性
  • 天然支持数组变化监听
  • 非侵入式拦截,不需要修改原对象
  • 可以拦截更多操作(如indelete函数调用等)

二、Proxy基础:拦截器实战

Proxy的基本用法是创建一个代理对象,通过拦截器(handler)定义拦截行为。以下是响应式系统中常用的拦截器:

// 原始对象
const target = {name: 'Vue',version: 3,features: ['reactive', 'composition-api']
};// 创建代理对象
const handler = {// 拦截属性访问:obj.prop 或 obj[prop]get(target, prop, receiver) {console.log(`访问属性: ${prop}`);return Reflect.get(target, prop, receiver);},// 拦截属性赋值:obj.prop = valueset(target, prop, value, receiver) {console.log(`设置属性: ${prop} = ${value}`);return Reflect.set(target, prop, value, receiver);},// 拦截属性删除:delete obj.propdeleteProperty(target, prop) {console.log(`删除属性: ${prop}`);return Reflect.deleteProperty(target, prop);},// 拦截in操作符:prop in objhas(target, prop) {console.log(`检查属性是否存在: ${prop}`);return Reflect.has(target, prop);}
};const proxy = new Proxy(target, handler);// 测试拦截器
proxy.name; // 访问属性: name → 返回 'Vue'
proxy.version = 3.3; // 设置属性: version = 3.3 → 返回 true
'features' in proxy; // 检查属性是否存在: features → 返回 true
delete proxy.version; // 删除属性: version → 返回 true

Reflect是ES6引入的内置对象,提供了方法,确保拦截操作的默认行为可以正确执行,同时返回规范的结果。

三、实现简易响应式系统

Vue 3的响应式系统核心是"依赖收集-触发更新"机制:当访问响应式数据时收集依赖(副作用函数),当数据变化时触发这些依赖重新执行。

1. 依赖收集容器

首先需要一个容器存储依赖,以及一个方法追踪当前活跃的副作用函数:

// 存储依赖的容器:key为目标对象,value为该对象的依赖映射
const targetMap = new WeakMap();// 当前活跃的副作用函数
let activeEffect = null;// 追踪副作用函数
function track(target, prop) {if (!activeEffect) return;// 获取目标对象的依赖映射let depsMap = targetMap.get(target);if (!depsMap) {targetMap.set(target, (depsMap = new Map()));}// 获取属性的依赖集合let dep = depsMap.get(prop);if (!dep) {depsMap.set(prop, (dep = new Set()));}// 将当前副作用函数添加到依赖集合dep.add(activeEffect);
}// 触发依赖更新
function trigger(target, prop) {const depsMap = targetMap.get(target);if (!depsMap) return;const dep = depsMap.get(prop);if (dep) {// 执行所有依赖的副作用函数dep.forEach(effect => effect());}
}

使用WeakMap存储目标对象的依赖,避免内存泄漏;使用Set存储副作用函数,自动去重。

2. 创建响应式对象

结合Proxy和依赖收集,实现响应式对象创建函数:

// 创建响应式对象
function reactive(target) {// 仅对对象和数组进行代理if (typeof target !== 'object' || target === null) {return target;}// 创建代理对象const handler = {get(target, prop, receiver) {const result = Reflect.get(target, prop, receiver);// 收集依赖track(target, prop);// 递归处理嵌套对象return reactive(result);},set(target, prop, value, receiver) {const oldValue = Reflect.get(target, prop, receiver);// 如果值未变化,不触发更新if (oldValue === value) return true;const result = Reflect.set(target, prop, value, receiver);// 触发更新trigger(target, prop);return result;},deleteProperty(target, prop) {const result = Reflect.deleteProperty(target, prop);// 触发更新trigger(target, prop);return result;}};return new Proxy(target, handler);
}

这里的关键是:

  • 递归处理嵌套对象,确保深层属性也具有响应性
  • get中收集依赖,在setdeleteProperty中触发更新
  • 避免值未变化时的无效更新

3. 副作用函数注册

实现effect函数,用于注册依赖响应式数据的副作用函数:

// 注册副作用函数
function effect(fn) {// 保存当前副作用函数activeEffect = fn;// 执行函数,触发依赖收集fn();// 重置当前副作用函数activeEffect = null;
}

四、实战测试:响应式系统工作流程

通过一个完整示例测试上述实现:

// 创建响应式对象
const state = reactive({count: 0,user: {name: 'Alice'},hobbies: ['reading']
});// 注册副作用函数1:监听count变化
effect(() => {console.log(`count变化: ${state.count}`);
});// 注册副作用函数2:监听user.name变化
effect(() => {console.log(`用户名变化: ${state.user.name}`);
});// 测试基本操作
state.count++; // 触发副作用1 → 输出 "count变化: 1"
state.user.name = 'Bob'; // 触发副作用2 → 输出 "用户名变化: Bob"// 测试新增属性
state.age = 20; // 新增属性,不触发任何副作用
effect(() => {console.log(`年龄变化: ${state.age}`); // 注册新副作用
});
state.age = 21; // 触发新增的副作用 → 输出 "年龄变化: 21"// 测试删除属性
delete state.age; // 触发副作用 → 输出 "年龄变化: undefined"// 测试数组操作
effect(() => {console.log(`爱好列表: ${state.hobbies.join(',')}`);
});
state.hobbies.push('coding'); // 触发数组副作用 → 输出 "爱好列表: reading,coding"
state.hobbies[0] = 'writing'; // 触发数组副作用 → 输出 "爱好列表: writing,coding"

这个示例完整展示了响应式系统的工作流程:

  1. reactive将普通对象转换为响应式代理
  2. effect注册副作用函数,执行时触发get拦截器
  3. get拦截器调用track收集依赖
  4. 数据变化时触发setdeleteProperty拦截器
  5. 拦截器调用trigger执行所有相关的副作用函数

五、Vue 3响应式的高级特性

实际的Vue 3响应式系统还包含更多优化和特性:

1. 只读代理

使用readonly创建只读响应式对象,禁止修改操作:

function readonly(target) {return new Proxy(target, {get(target, prop, receiver) {const result = Reflect.get(target, prop, receiver);return readonly(result); // 递归处理},set(target, prop, value, receiver) {console.warn(`Cannot set property ${prop} of readonly object`);return false;},deleteProperty(target, prop) {console.warn(`Cannot delete property ${prop} of readonly object`);return false;}});
}// 使用示例
const info = readonly({ name: 'Vue 3' });
info.name = 'Vue 4'; // 警告:Cannot set property name of readonly object

2. 浅层响应式

shallowReactive只处理对象本身的响应性,不递归处理嵌套对象:

function shallowReactive(target) {return new Proxy(target, {get(target, prop, receiver) {track(target, prop);return Reflect.get(target, prop, receiver); // 不递归},set(target, prop, value, receiver) {const result = Reflect.set(target, prop, value, receiver);trigger(target, prop);return result;}});
}

适用于已知嵌套对象不会变化的场景,提升性能。

3. 响应式判断工具

Vue 3提供了工具函数判断对象是否为响应式:

// 标记响应式对象
const reactiveFlag = Symbol('isReactive');function reactive(target) {// ...const handler = {get(target, prop, receiver) {if (prop === reactiveFlag) return true;// ...}// ...};
}// 判断是否为响应式对象
function isReactive(value) {return value && value[reactiveFlag] === true;
}

六、总结

Vue 3基于Proxy的响应式系统通过拦截对象操作,实现了更全面、更高效的响应式能力。核心流程是:

  1. 使用reactive创建代理对象,通过Proxy拦截属性访问和修改
  2. 访问属性时,track函数收集依赖的副作用函数
  3. 修改属性时,trigger函数触发所有相关的副作用函数执行

相比Vue 2的实现,Proxy方案解决了对象新增/删除属性监听、数组变化监听等问题,同时通过懒递归(访问时才处理嵌套对象)提升了性能。