Vue3 的响应式系统基于 ES6 的
Proxy
实现,其核心原理是通过代理对象拦截属性的读取和修改等操作,从而自动追踪依赖关系并触发更新。以下是详细说明和代码实现:
一、Proxy 原理解析
-
Proxy 基本概念
Proxy 是 JavaScript 的元编程特性,允许创建一个代理对象,拦截并自定义对象的基本操作(如属性访问、赋值等)。 -
Vue3 响应式流程
二、核心代码实现
- 实现简易 reactive 函数
// 存储依赖的容器(WeakMap 防止内存泄漏)
const targetMap = new WeakMap();
let activeEffect = null; // 当前活动的副作用函数
// 响应式入口
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) {
Reflect.set(target, key, value, receiver);
trigger(target, key);
return true;
}
});
}
// 依赖收集
function track(target, key) {
if (!activeEffect) return;
let depsMap = targetMap.get(target);
if (!depsMap) {
depsMap = new Map();
targetMap.set(target, depsMap);
}
let dep = depsMap.get(key);
if (!dep) {
dep = new Set();
depsMap.set(key, dep);
}
dep.add(activeEffect); // 记录副作用函数
}
// 触发更新
function trigger(target, key) {
const depsMap = targetMap.get(target);
if (!depsMap) return;
const dep = depsMap.get(key);
if (dep) {
dep.forEach(effect => effect()); // 执行所有依赖的副作用函数
}
}
// 副作用系统
function effect(fn) {
activeEffect = fn;
fn(); // 执行首次收集依赖
activeEffect = null;
}
- 测试用例
const obj = reactive({ count: 0 });
// 副作用函数(类似 Vue 的 watchEffect)
effect(() => {
console.log(`当前计数: ${obj.count}`);
});
obj.count++; // 输出 "当前计数: 1"
obj.count++; // 输出 "当前计数: 2"
三、功能增强实现
- 嵌套对象处理
function reactive(target) {
return new Proxy(target, {
get(target, key, receiver) {
const res = Reflect.get(target, key, receiver);
track(target, key);
// 递归代理嵌套对象
return typeof res === 'object' && res !== null ? reactive(res) : res;
},
// set 方法同上...
});
}
// 测试嵌套对象
const nested = reactive({ foo: { bar: 1 } });
effect(() => {
console.log(nested.foo.bar); // 自动收集 nested.foo.bar 的依赖
});
- 数组处理
function reactive(target) {
return new Proxy(target, {
get(target, key, receiver) {
// 代理数组方法
if (Array.isArray(target) && ['push', 'pop'].includes(key)) {
return function(...args) {
const result = Array.prototype[key].apply(target, args);
trigger(target, 'length'); // 手动触发更新
return result;
}
}
// 其他逻辑同上...
}
});
}
四、Proxy vs Object.defineProperty
特性 | Vue3 Proxy | Vue2 Object.defineProperty |
---|---|---|
监听范围 | 支持动态新增/删除属性 | 只能监听已存在的属性 |
数组处理 | 直接监听索引变更和长度变化 | 需要重写数组方法 |
嵌套对象 | 自动按需代理深层属性 | 必须初始化时递归转换所有属性 |
性能表现 | 内存占用更低, 处理速度更快 | 大规模数据响应时性能较低 |
数据类型支持 | 支持 Map/Set/WeakMap 等ES6数据结构 | 只支持普通对象/数组 |
IE兼容性 | 不支持 IE11 及以下 | 支持 IE9+ |
五、完整示例代码
<!DOCTYPE html>
<html>
<body>
<script>
// ========== 响应式系统核心 ==========
const targetMap = new WeakMap();
let activeEffect = null;
function reactive(target) {
return new Proxy(target, {
get(target, key, receiver) {
const res = Reflect.get(target, key, receiver);
track(target, key);
return typeof res === 'object' ? reactive(res) : res;
},
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;
}
});
}
function track(target, key) { /* 同上 */ }
function trigger(target, key) { /* 同上 */ }
function effect(fn) { /* 同上 */ }
// ========== 测试用例 ==========
const state = reactive({
user: { name: 'John', age: 25 },
scores: [80, 90, 95]
});
effect(() => {
console.log('用户姓名:', state.user.name);
});
effect(() => {
console.log('分数列表:', state.scores.join(','));
});
// 触发更新示例
setTimeout(() => state.user.name = 'Alice', 1000);
setTimeout(() => state.scores.push(100), 2000);
</script>
</body>
</html>
六、总结
-
核心优势
Proxy 提供更全面的拦截能力,天然支持动态属性、集合类型和嵌套对象自动代理,大幅简化了 Vue 的响应式实现。 -
生产应用
实际在 Vue3 中会通过reactive()
创建深度响应式对象,通过ref()
处理基本类型,并通过computed()
和watch()
构建完整的响应式系统。 -
性能优化
Vue3 通过 Proxy 实现对依赖的精准追踪(按属性级追踪),相比 Vue2 的全属性遍历具有显著性能优势。
Proxy对象说明
以下是关于 ES6 Proxy 的详细说明,包含核心概念、使用方法和实际应用示例:
ES6 Proxy 对象详解
一、Proxy 核心概念
-
基本定义
Proxy(代理)是 ES6 引入的元编程特性,用于创建对象的代理,从而拦截和自定义对象的基本操作。 -
关键术语
• Target:被代理的原始对象
• Handler:包含拦截器(traps)的对象
• Trap:拦截特定操作的方法(如 get/set)
二、基础使用方法
- 创建 Proxy
const target = { name: 'John' };
const handler = {
get(target, prop) {
return target[prop] || 'Not Found';
}
};
const proxy = new Proxy(target, handler);
console.log(proxy.name); // "John"
console.log(proxy.age); // "Not Found"
- 常用拦截器方法
拦截器 | 触发场景 | 示例用法 |
---|---|---|
get(target, prop) | 读取属性时 | 属性访问控制、计算属性 |
set(target, prop, value) | 设置属性时 | 数据验证、自动更新机制 |
has(target, prop) | in 操作符 | 隐藏属性 |
deleteProperty(target, prop) | delete 操作符 | 防止删除关键属性 |
apply(target, thisArg, args) | 函数调用时 | 函数增强/装饰器 |
construct(target, args) | new 操作符 | 单例模式控制 |
三、进阶应用示例
- 数据验证代理
const validator = {
set(target, prop, value) {
if (prop === 'age') {
if (!Number.isInteger(value) || value < 0) {
throw new TypeError('Invalid age value');
}
}
target[prop] = value;
return true; // 表示设置成功
}
};
const person = new Proxy({}, validator);
person.age = 25; // 正常
person.age = '25'; // 抛出 TypeError
- 自动格式化代理
const formatter = {
get(target, prop) {
const value = target[prop];
return typeof value === 'number'
? value.toLocaleString('en-US', { style: 'currency', currency: 'USD' })
: value;
}
};
const account = new Proxy({ balance: 1500 }, formatter);
console.log(account.balance); // "$1,500.00"
- 函数执行代理
function sum(a, b) { return a + b }
const logger = {
apply(target, thisArg, args) {
console.log(`Calling ${target.name} with args: ${args}`);
const result = target(...args);
console.log(`Result: ${result}`);
return result;
}
};
const loggedSum = new Proxy(sum, logger);
loggedSum(2, 3);
// 输出:
// Calling sum with args: 2,3
// Result: 5
四、实际应用场景
- 观察者模式实现
class Observable {
constructor() {
this.observers = [];
return new Proxy(this, {
set(target, prop, value) {
const oldValue = target[prop];
target[prop] = value;
if (oldValue !== value) {
target.observers.forEach(fn => fn(prop, value));
}
return true;
}
});
}
subscribe(fn) {
this.observers.push(fn);
}
}
const user = new Observable();
user.subscribe((key, val) => console.log(`${key} changed to ${val}`));
user.name = 'Alice'; // 输出 "name changed to Alice"
- REST API 客户端
const api = new Proxy({}, {
get(target, endpoint) {
return async (params) => {
const res = await fetch(`https://api.example.com/${endpoint}`, {
method: 'POST',
body: JSON.stringify(params)
});
return res.json();
}
}
});
// 使用方式
api.users({ id: 1 }).then(console.log);
api.products({ category: 'books' }).then(console.log);
五、注意事项
-
代理穿透
使用Proxy.revocable()
创建可撤销代理:const {proxy, revoke} = Proxy.revocable(target, handler); revoke(); // 后续操作会抛出 TypeError
-
性能影响
• 代理操作比直接对象操作慢约 50%• 避免在性能关键路径过度使用
-
浏览器兼容性
六、与 Object.defineProperty 对比
特性 | Proxy | Object.defineProperty |
---|---|---|
拦截操作范围 | 支持13种操作 | 只能拦截属性读写 |
数组处理 | 直接拦截数组方法 | 需要重写数组原型方法 |
属性动态性 | 自动处理新增/删除属性 | 必须预先定义属性 |
嵌套对象处理 | 支持深层代理 | 需要递归初始化 |
性能 | 首次初始化快,操作略慢 | 初始化慢,操作快 |
通过 Proxy 可以实现许多高级编程模式,但需要合理评估使用场景,避免不必要的性能损耗。