元编程

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

包含陷阱的占位符对象。

陷阱

提供属性访问的方法。(这类似于操作系统中的陷阱概念。)

目标

代理虚拟化的对象。它通常用作代理的存储后端。关于对象不可扩展或不可配置属性的不变性(语义保持不变)将针对目标进行验证。

不变性

在实现自定义操作时保持不变的语义称为不变性。如果你违反了处理程序的不变性,将会抛出一个 TypeError

处理程序和陷阱

可撤销的 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
}