元编程
Proxy 和 Reflect 对象使得你可以拦截并为基本语言操作定义自定义行为(例如,属性查找、赋值、枚举、函数调用等)。在这两个对象的帮助下,你可以在 JavaScript 的元级别进行编程。
代理
Proxy 对象允许你拦截某些操作并实现自定义行为。
例如,获取对象的属性:
const handler = {
get(target, name) {
return name in target ? target[name] : 42;
},
};
const p = new Proxy({}, handler);
p.a = 1;
console.log(p.a, p.b); // 1, 42
Proxy 对象定义了一个 target(此处为一个空对象)和一个 handler 对象,其中实现了一个 get 陷阱。在这里,一个被代理的对象在获取未定义的属性时不会返回 undefined,而是会返回数字 42。
更多示例可在 Proxy 参考页面上找到。
术语
在讨论代理的功能时,会使用以下术语。
- handler
-
包含陷阱的占位符对象。
- traps
-
提供属性访问的方法。(这类似于操作系统中陷阱的概念。)
- 目标
-
代理所虚拟化的对象。它通常用作代理的存储后端。关于对象不可扩展性或不可配置属性的不变量(保持不变的语义)会根据目标进行验证。
- invariants
-
在实现自定义操作时保持不变的语义被称为不变量。如果违反了处理器的不变量,将会抛出
TypeError。
处理器与陷阱
下表总结了 Proxy 对象可用的陷阱。详细解释和示例请参见参考页面。
可撤销的 Proxy
Proxy.revocable() 方法用于创建一个可撤销的 Proxy 对象。这意味着代理可以通过函数 revoke 被撤销,并关闭该代理。
之后,对代理的任何操作都会导致 TypeError。
const revocable = Proxy.revocable(
{},
{
get(target, name) {
return `[[${name}]]`;
},
},
);
const proxy = revocable.proxy;
console.log(proxy.foo); // "[[foo]]"
revocable.revoke();
console.log(proxy.foo); // TypeError: Cannot perform 'get' on a proxy that has been revoked
proxy.foo = 1; // TypeError: Cannot perform 'set' on a proxy that has been revoked
delete proxy.foo; // TypeError: Cannot perform 'deleteProperty' on a proxy that has been revoked
console.log(typeof proxy); // "object", typeof doesn't trigger any trap
反射
Reflect 是一个内置对象,它提供了可拦截 JavaScript 操作的方法。这些方法与代理处理器的方法相同。
Reflect 不是一个函数对象。
Reflect 有助于将默认操作从处理器转发到 target。
例如,使用 Reflect.has(),你可以将 in 运算符作为一个函数来使用。
Reflect.has(Object, "assign"); // true
一个更好的 apply() 函数
在 Reflect 出现之前,你通常使用 Function.prototype.apply() 方法来调用一个函数,并指定一个 this 值和以数组(或类数组对象)形式提供的 arguments。
Function.prototype.apply.call(Math.floor, undefined, [1.75]);
使用 Reflect.apply,这变得不那么冗长,也更容易理解。
Reflect.apply(Math.floor, undefined, [1.75]);
// 1
Reflect.apply(String.fromCharCode, undefined, [104, 101, 108, 108, 111]);
// "hello"
Reflect.apply(RegExp.prototype.exec, /ab/, ["confabulation"]).index;
// 4
Reflect.apply("".charAt, "ponies", [3]);
// "i"
检查属性定义是否成功
使用 Object.defineProperty 时,如果成功,它会返回一个对象,否则会抛出一个 TypeError,因此你需要使用 try...catch 块来捕获在定义属性时发生的任何错误。而 Reflect.defineProperty() 返回一个布尔类型的成功状态,所以你只需使用一个 if...else 块即可。
if (Reflect.defineProperty(target, property, attributes)) {
// success
} else {
// failure
}