元编程
===

* « 上一页
* 下一页 »

`Proxy` 和 `Reflect` 对象允许你拦截并自定义基本语言操作（例如属性查找、赋值、枚举和函数调用等）。借助这两个对象，你可以在 JavaScript 进行元级别的编程。

代理
--

`Proxy` 对象可以拦截某些操作并实现自定义行为。

例如获取一个对象上的属性：


```js
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`。

更多例子参见 `Proxy` 页面。

### 术语

在讨论代理的功能时会用到以下术语：

`handler`

包含陷阱的占位符对象（下译作“处理器”）。

陷阱

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

`target`

代理所虚拟化的对象（下译作“目标”）。它通常用作代理的存储后端。JavaScript 会验证与不可扩展性或不可配置属性相关的不变式。

不变式

实现自定义操作时保持不变的语义称为*不变式*。如果你破坏了处理器的不变式，则会引发 `TypeError` 异常。

处理器和陷阱
------

以下表格中总结了 `Proxy` 对象可用的陷阱。详细的解释和例子请看参考页。

| 处理器 / 陷阱 | 拦截的操作 | 不变式 |
| --- | --- | --- |
| `handler.getPrototypeOf()` | `Object.getPrototypeOf()``Reflect.getPrototypeOf()``__proto__``Object.prototype.isPrototypeOf()``instanceof` | * `getPrototypeOf` 方法必须返回一个对象或 `null`。
* 如果 `target` 不可扩展，`Object.getPrototypeOf(proxy)`
 必须返回和 `Object.getPrototypeOf(target)`
 一样的值。
 |
| `handler.setPrototypeOf()` | `Object.setPrototypeOf()``Reflect.setPrototypeOf()` | 
 如果 `target` 不可扩展，参数 `prototype`
 必须与 `Object.getPrototypeOf(target)` 的值相同。
  |
| `handler.isExtensible()` | `Object.isExtensible()``Reflect.isExtensible()` | `Object.isExtensible(proxy)` 必须返回和
 `Object.isExtensible(target)` 一样的值。
  |
| `handler.preventExtensions()` | `Object.preventExtensions()``Reflect.preventExtensions()` | 
 如果 `Object.isExtensible(proxy)`
 值为 `false`，那么
 `Object.preventExtensions(proxy)` 只可能返回
 `true`。
  |
| `handler.getOwnPropertyDescriptor()` | `Object.getOwnPropertyDescriptor()``Reflect.getOwnPropertyDescriptor()` | * `getOwnPropertyDescriptor` 必须返回对象或者
 `undefined`。
* 如果存在一个对应于 `target`
 的属性是不可配置的自有属性，那么该属性不能被报告为不存在的。
* 如果存在一个对应于 `target`
 的属性是自有属性，且该
 `target` 不可扩展，那么该属性不能被报告为不存在的。
* 如果并不存在一个对应于 `target`
 的属性是自有属性，且该
 `target` 不可扩展，那么该属性不能被报告为存在的。
* 如果并不存在一个对应于 `target`
 的属性是自有属性，或存在一个对应于 `target`
 的属性是可配置的自有属性，那么它不能被报告为不可配置的。
* `Object.getOwnPropertyDescriptor(target)`
 的结果可以通过 `Object.defineProperty` 应用到
 `target` 上，且不会抛出异常。
 |
| `handler.defineProperty()` | `Object.defineProperty()``Reflect.defineProperty()` | * 如果 `target` 不可扩展，那么就不能添加属性。
* 如果并不存在一个对应于 `target`
 的属性是不可配置的自有属性，那么就不能添加（或修改）该属性为不可配置的。
* 如果存在一个对应于 `target`
 的属性是可配置的，那么这个属性未必是不可配置的。
* 如果存在一个对应于 `target` 的属性，那么
 `Object.defineProperty(target, prop,
descriptor)`
 将不会抛出异常。
* 在严格模式下，如果 `defineProperty` 处理器返回
 `false`，则会抛出 `TypeError` 异常。
 |
| `handler.has()` | 
属性查询
`foo in proxy`
继承属性查询
`foo in Object.create(proxy)``Reflect.has()`
 | * 如果存在一个对应于 `target`
 的属性是不可配置的自有属性，那么该属性不能被报告为不存在的。
* 如果存在一个对应于 `target`
 的属性是自有属性，且 `target`
 不可扩展，那么该属性不能被报告为不存在的。
 |
| `handler.get()` | 
属性访问
`proxy[foo]``proxy.bar`
继承属性访问
`Object.create(proxy)[foo]``Reflect.get()`
 | * 如果对应于 `target`
 的属性是不可写且不可配置的数据属性，那么该属性值必须与其相同。
* 如果对应于 `target`
 的属性是不可配置的访问器属性，且其 `[[Get]]`
 属性为 `undefined`，那么该属性值必须为
 `undefined`。
 |
| `handler.set()` | 
属性赋值
`proxy[foo] = bar``proxy.foo = bar`
继承属性赋值
`Object.create(proxy)[foo] = bar``Reflect.set()`
 | * 如果对应于 `target`
 的属性是不可写且不可配置的数据属性，那么就不能修改该属性的值使其不同于
 `target` 上对应属性的值。
* 如果对应于 `target`
 的属性是不可配置的访问器属性，且其 `[[Set]]`
 属性为 `undefined`，那么就不能设置该属性的值。
* 在严格模式下，如果 `set` 处理器返回
 `false`，则会抛出 `TypeError` 异常。
 |
| `handler.deleteProperty()` | 
属性删除
`delete proxy[foo]``delete proxy.foo``Reflect.deleteProperty()`
 | 
 如果存在一个对应于 `target`
 的属性是不可配置的自有属性，那么该属性不能被删除。
  |
| `handler.ownKeys()` | `Object.getOwnPropertyNames()``Object.getOwnPropertySymbols()``Object.keys()``Reflect.ownKeys()` | * `ownKeys` 的返回值是一个数组。
* 返回值中的每个元素类型为
 `String` 或 `Symbol`。
* 返回值中必须包含 `target`
 的所有不可配置自有属性的键名。
* 如果 `target` 不可扩展，那么返回值中必须有且仅有
 `target` 的所有自有属性的键名。
 |
| `handler.apply()` | `proxy(..args)``Function.prototype.apply()``Function.prototype.call()``Reflect.apply()` | 不存在关于 `handler.apply` 方法的不变式。 |
| `handler.construct()` | `new proxy(...args)``Reflect.construct()` | 返回值必须是一个 `Object`。 |

可撤销的 `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` 不会触发任何陷阱
```

反射
--

`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
}
```

* « 上一页
* 下一页 »