元编程

ProxyReflect 对象使得你可以拦截并为基本语言操作定义自定义行为(例如,属性查找、赋值、枚举、函数调用等)。在这两个对象的帮助下,你可以在 JavaScript 的元级别进行编程。

代理

Proxy 对象允许你拦截某些操作并实现自定义行为。

例如,获取对象的属性:

js
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 对象可用的陷阱。详细解释和示例请参见参考页面

处理器/陷阱 拦截的操作
handler.getPrototypeOf() Object.getPrototypeOf()
Reflect.getPrototypeOf()
__proto__
Object.prototype.isPrototypeOf()
instanceof
handler.setPrototypeOf() Object.setPrototypeOf()
Reflect.setPrototypeOf()
handler.isExtensible() Object.isExtensible()
Reflect.isExtensible()
handler.preventExtensions() Object.preventExtensions()
Reflect.preventExtensions()
handler.getOwnPropertyDescriptor() Object.getOwnPropertyDescriptor()
Reflect.getOwnPropertyDescriptor()
handler.defineProperty() Object.defineProperty()
Reflect.defineProperty()
handler.has()
属性查询
foo in proxy
继承属性查询
foo in Object.create(proxy)
Reflect.has()
handler.get()
属性访问
proxy[foo]
proxy.bar
继承属性访问
Object.create(proxy)[foo]
Reflect.get()
handler.set()
属性赋值
proxy[foo] = bar
proxy.foo = bar
继承属性赋值
Object.create(proxy)[foo] = bar
Reflect.set()
handler.deleteProperty()
属性删除
delete proxy[foo]
delete proxy.foo
Reflect.deleteProperty()
handler.ownKeys() Object.getOwnPropertyNames()
Object.getOwnPropertySymbols()
Object.keys()
Reflect.ownKeys()
handler.apply() proxy(..args)
Function.prototype.apply()Function.prototype.call()
Reflect.apply()
handler.construct() new proxy(...args)
Reflect.construct()

可撤销的 Proxy

Proxy.revocable() 方法用于创建一个可撤销的 Proxy 对象。这意味着代理可以通过函数 revoke 被撤销,并关闭该代理。

之后,对代理的任何操作都会导致 TypeError

js
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 运算符作为一个函数来使用。

js
Reflect.has(Object, "assign"); // true

一个更好的 apply() 函数

Reflect 出现之前,你通常使用 Function.prototype.apply() 方法来调用一个函数,并指定一个 this 值和以数组(或类数组对象)形式提供的 arguments

js
Function.prototype.apply.call(Math.floor, undefined, [1.75]);

使用 Reflect.apply,这变得不那么冗长,也更容易理解。

js
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 块即可。

js
if (Reflect.defineProperty(target, property, attributes)) {
  // success
} else {
  // failure
}