title | slug |
---|---|
元编程 |
Web/JavaScript/Guide/Meta_programming |
{{jsSidebar("JavaScript Guide")}} {{PreviousNext("Web/JavaScript/Guide/Iterators_and_generators", "Web/JavaScript/Guide/Modules")}}
{{jsxref("Proxy")}} 和 {{jsxref("Reflect")}} 对象允许你拦截并自定义基本语言操作(例如属性查找、赋值、枚举和函数调用等)。借助这两个对象,你可以在 JavaScript 进行元级别的编程。
{{jsxref("Proxy")}} 对象可以拦截某些操作并实现自定义行为。
例如获取一个对象上的属性:
let handler = {
get(target, name) {
return name in target ? target[name] : 42;
},
};
let p = new Proxy({}, handler);
p.a = 1;
console.log(p.a, p.b); // 1, 42
Proxy
对象定义了一个 target
(这里是一个空对象)和一个实现了 get
陷阱的 handler
对象。这里,代理的对象在获取未定义的属性时不会返回 undefined
,而是返回 42
。
更多例子参见 {{jsxref("Proxy")}} 页面。
在讨论代理的功能时会用到以下术语:
- {{jsxref("Proxy/Proxy", "handler")}}
- : 包含陷阱的占位符对象(下译作“处理器”)。
- 陷阱
- : 提供属性访问的方法(这类似于操作系统中陷阱的概念)。
target
- : 代理所虚拟化的对象(下译作“目标”)。它通常用作代理的存储后端。JavaScript 会验证与不可扩展性或不可配置属性相关的不变式。
- 不变式
- : 实现自定义操作时保持不变的语义称为不变式。如果你破坏了处理器的不变式,则会引发 {{jsxref("TypeError")}} 异常。
以下表格中总结了 Proxy
对象可用的陷阱。详细的解释和例子请看{{jsxref("Proxy/Proxy", "参考页", "", 1)}}。
处理器 / 陷阱 | 拦截的操作 | 不变式 |
---|---|---|
{{jsxref("Proxy/Proxy/getPrototypeOf", "handler.getPrototypeOf()")}} |
{{jsxref("Object.getPrototypeOf()")}} {{jsxref("Reflect.getPrototypeOf()")}} {{jsxref("Object/proto", "__proto__")}} {{jsxref("Object.prototype.isPrototypeOf()")}} {{jsxref("Operators/instanceof", "instanceof")}} |
|
{{jsxref("Proxy/Proxy/setPrototypeOf", "handler.setPrototypeOf()")}} |
{{jsxref("Object.setPrototypeOf()")}} {{jsxref("Reflect.setPrototypeOf()")}} |
如果 target 不可扩展,参数 prototype
必须与 Object.getPrototypeOf(target) 的值相同。
|
{{jsxref("Proxy/Proxy/isExtensible", "handler.isExtensible()")}} |
{{jsxref("Object.isExtensible()")}} {{jsxref("Reflect.isExtensible()")}} |
Object.isExtensible(proxy) 必须返回和
Object.isExtensible(target) 一样的值。
|
{{jsxref("Proxy/Proxy/preventExtensions", "handler.preventExtensions()")}} |
{{jsxref("Object.preventExtensions()")}} {{jsxref("Reflect.preventExtensions()")}} |
如果 Object.isExtensible(proxy)
值为 false ,那么
Object.preventExtensions(proxy) 只可能返回
true 。
|
{{jsxref("Proxy/Proxy/getOwnPropertyDescriptor", "handler.getOwnPropertyDescriptor()")}} |
{{jsxref("Object.getOwnPropertyDescriptor()")}} {{jsxref("Reflect.getOwnPropertyDescriptor()")}} |
|
{{jsxref("Proxy/Proxy/defineProperty", "handler.defineProperty()")}} |
{{jsxref("Object.defineProperty()")}} {{jsxref("Reflect.defineProperty()")}} |
|
{{jsxref("Proxy/Proxy/has", "handler.has()")}} |
|
|
{{jsxref("Proxy/Proxy/get", "handler.get()")}} |
|
|
{{jsxref("Proxy/Proxy/set", "handler.set()")}} |
|
|
{{jsxref("Proxy/Proxy/deleteProperty", "handler.deleteProperty()")}} |
|
如果存在一个对应于 target
的属性是不可配置的自有属性,那么该属性不能被删除。
|
{{jsxref("Proxy/Proxy/ownKeys", "handler.ownKeys()")}} |
{{jsxref("Object.getOwnPropertyNames()")}} {{jsxref("Object.getOwnPropertySymbols()")}} {{jsxref("Object.keys()")}} {{jsxref("Reflect.ownKeys()")}} |
|
{{jsxref("Proxy/Proxy/apply", "handler.apply()")}} |
proxy(..args) {{jsxref("Function.prototype.apply()")}} {{jsxref("Function.prototype.call()")}} {{jsxref("Reflect.apply()")}} |
不存在关于 handler.apply 方法的不变式。
|
{{jsxref("Proxy/Proxy/construct", "handler.construct()")}} |
new proxy(...args) {{jsxref("Reflect.construct()")}} |
返回值必须是一个 Object 。 |
可以用 {{jsxref("Proxy.revocable()")}} 方法来创建可撤销的 Proxy
对象。这意味着可以通过 revoke
函数来撤销并关闭一个代理。
此后,对代理进行的任意的操作都会导致 {{jsxref("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` 不会触发任何陷阱
{{jsxref("Reflect")}} 是一个内置对象,它为可拦截的 JavaScript 操作提供了方法。这些方法与{{jsxref("Proxy/Proxy", "代理处理器所提供的方法", "", 1)}}类似。
Reflect
并不是一个函数对象。
Reflect
将默认操作从处理器转发到 target
。
以 {{jsxref("Reflect.has()")}} 为例,你可以将 in
运算符作为函数:
Reflect.has(Object, "assign"); // true
在不借助 Reflect
的情况下,我们通常使用 {{jsxref("Function.prototype.apply()")}} 方法调用一个具有给定 this
值和 arguments
数组(或类数组对象)的函数。
Function.prototype.apply.call(Math.floor, undefined, [1.75]);
借助 {{jsxref("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"
使用 {{jsxref("Object.defineProperty")}},如果成功则返回一个对象,否则抛出一个 {{jsxref("TypeError")}},你可使用 {{jsxref("Statements/try...catch", "try...catch")}} 块来捕获定义属性时发生的任何错误。因为 {{jsxref("Reflect.defineProperty")}} 返回一个布尔值表示的成功状态,你可以在这里使用 {{jsxref("Statements/if...else", "if...else")}} 块:
if (Reflect.defineProperty(target, property, attributes)) {
// success
} else {
// failure
}
{{PreviousNext("Web/JavaScript/Guide/Iterators_and_generators", "Web/JavaScript/Guide/Modules")}}