ES6 引入的 Proxy
是一个非常强大的元编程(Metaprogramming)特性。它允许你创建一个代理对象,用于拦截并自定义对另一个对象(目标对象)的基本操作(如属性读取、赋值、枚举、函数调用等)。你可以把它想象成一个对象的“守门人”或“中间件”,所有对目标对象的操作都必须先经过这个“守门人”的检查和处理。
1. 基本语法
const proxy = new Proxy(target, handler);
target
: 要被代理的原始对象。可以是任何类型的对象(普通对象、数组、函数、甚至另一个代理)。handler
: 一个对象,其属性定义了在执行各种操作时所对应的拦截器(traps)。这些拦截器是特殊的方法名(如get
,set
,has
等)。proxy
: 返回的代理对象。你应该操作这个代理对象,而不是直接操作target
。
2. Proxy 可以实现的核心功能
通过在 handler
中定义不同的“陷阱”(traps),Proxy 可以实现多种高级功能:
(1) 属性访问拦截 (get
trap)
拦截对对象属性的读取操作。
- 实现数据绑定/响应式系统:
这是 Vue 3 响应式系统的核心原理之一。当读取属性时,可以收集依赖(哪个组件/函数用到了这个属性)。const target = { message: 'Hello' };
const handler = {
get(obj, prop) {
console.log(`读取属性: ${prop}`);
return obj[prop];
}
};
const proxy = new Proxy(target, handler);
console.log(proxy.message); // 输出: 读取属性: message \n Hello - 提供默认值或计算属性:
const handler = { get(obj, prop) { if (prop in obj) { return obj[prop]; } else { return `Property '${prop}' not found, returning default.`; } } }; const proxy = new Proxy({}, handler); console.log(proxy.name); // Property 'name' not found, returning default.
(2) 属性赋值拦截 (set
trap)
拦截对对象属性的设置操作。
- 数据验证:
const validator = { set(obj, prop, value) { if (prop === 'age') { if (typeof value !== 'number' || value < 0) { throw new Error('Age must be a positive number'); } } obj[prop] = value; return true; // 必须返回 true 表示设置成功 } }; const person = new Proxy({}, validator); person.age = 25; // OK // person.age = -5; // 抛出错误
- 实现响应式更新:
const handler = { set(obj, prop, value) { console.log(`设置属性: ${prop} = ${value}`); obj[prop] = value; // 在这里可以触发视图更新 (如 Vue 3) updateView(); return true; } };
- 自动类型转换:
const handler = { set(obj, prop, value) { if (prop === 'price') { obj[prop] = Number(value); // 确保 price 是数字 } else { obj[prop] = value; } return true; } };
(3) 属性删除拦截 (deleteProperty
trap)
拦截 delete
操作。
const handler = {
deleteProperty(obj, prop) {
if (prop.startsWith('_')) {
throw new Error('Private properties cannot be deleted');
}
delete obj[prop];
return true;
}
};
const obj = { _private: 'secret', public: 'ok' };
const proxy = new Proxy(obj, handler);
// delete proxy._private; // 抛出错误
delete proxy.public; // OK
(4) 属性存在性检查拦截 (has
trap)
拦截 in
操作符。
const handler = {
has(obj, prop) {
if (prop.startsWith('_')) {
return false; // 隐藏以 _ 开头的“私有”属性
}
return prop in obj;
}
};
const obj = { _secret: 'hidden', visible: 'shown' };
const proxy = new Proxy(obj, handler);
console.log('_secret' in proxy); // false
console.log('visible' in proxy); // true
(5) 枚举拦截 (ownKeys
, getOwnPropertyDescriptor
traps)
拦截 Object.keys()
, for...in
等操作。
const handler = {
ownKeys(obj) {
// 只返回非下划线开头的属性名
return Object.keys(obj).filter(key => !key.startsWith('_'));
}
};
const obj = { _hidden: 'no', shown: 'yes' };
const proxy = new Proxy(obj, handler);
console.log(Object.keys(proxy)); // ['shown']
(6) 函数调用拦截 (apply
trap)
当代理的目标是一个函数时,可以拦截其调用。
function sum(a, b) {
return a + b;
}
const handler = {
apply(target, thisArg, argumentsList) {
console.log(`调用函数 ${target.name},参数:`, argumentsList);
return target.apply(thisArg, argumentsList) * 2; // 返回值翻倍
}
};
const doubleSum = new Proxy(sum, handler);
console.log(doubleSum(1, 2)); // 输出: 调用函数 sum,参数: [1, 2] \n 6
(7) 构造函数拦截 (construct
trap)
当代理的目标是一个构造函数时,可以拦截 new
操作。
function Person(name) {
this.name = name;
}
const handler = {
construct(target, argumentsList) {
console.log(`创建 Person 实例,名字: ${argumentsList[0]}`);
return new target(...argumentsList);
}
};
const ProxiedPerson = new Proxy(Person, handler);
const alice = new ProxiedPerson('Alice'); // 输出: 创建 Person 实例,名字: Alice
3. Proxy 的实际应用场景
- 实现响应式框架:如 Vue 3 的核心就是基于
Proxy
构建的,它能高效地监听对象的所有属性变化。 - 数据验证库:在设置对象属性时自动进行类型、范围等校验。
- 调试和日志:为对象操作添加日志,方便调试。
- 访问控制:实现“私有”属性、只读属性或权限控制。
- 性能优化:实现懒加载、缓存(Memoization)。
- API 代理/适配器:拦截并修改对外部 API 的调用。
- 模拟对象 (Mocking):在测试中创建模拟对象。
4. 注意事项
- 性能:
Proxy
会带来一定的性能开销,因为它在每次操作时都要执行拦截器函数。对于性能敏感的场景需谨慎使用。 - 兼容性:
Proxy
不支持 IE 浏览器。 - 复杂性:过度使用
Proxy
可能使代码逻辑变得难以追踪和调试。 this
问题:在apply
和construct
陷阱中要特别注意this
的绑定。
总结
ES6 的 Proxy
是一个元编程工具,它通过拦截对目标对象的底层操作,赋予了开发者前所未有的控制能力。它可以用来实现数据绑定、验证、日志、访问控制、函数拦截等多种高级功能。
它是现代 JavaScript 框架(如 Vue 3)实现响应式系统的关键技术。理解 Proxy
的核心是理解其 handler
对象中的各种“陷阱”(traps)方法,它们定义了如何拦截和自定义对象的行为。
THE END