Skip to content

Latest commit

 

History

History
442 lines (402 loc) · 15.1 KB

File metadata and controls

442 lines (402 loc) · 15.1 KB
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")}}
  • getPrototypeOf 方法必须返回一个对象或 null
  • 如果 target 不可扩展,Object.getPrototypeOf(proxy) 必须返回和 Object.getPrototypeOf(target) 一样的值。
{{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()")}}
  • getOwnPropertyDescriptor 必须返回对象或者 undefined
  • 如果存在一个对应于 target 的属性是不可配置的自有属性,那么该属性不能被报告为不存在的。
  • 如果存在一个对应于 target 的属性是自有属性,且该 target 不可扩展,那么该属性不能被报告为不存在的。
  • 如果并不存在一个对应于 target 的属性是自有属性,且该 target 不可扩展,那么该属性不能被报告为存在的。
  • 如果并不存在一个对应于 target 的属性是自有属性,或存在一个对应于 target 的属性是可配置的自有属性,那么它不能被报告为不可配置的。
  • Object.getOwnPropertyDescriptor(target) 的结果可以通过 Object.defineProperty 应用到 target 上,且不会抛出异常。
{{jsxref("Proxy/Proxy/defineProperty", "handler.defineProperty()")}} {{jsxref("Object.defineProperty()")}}
{{jsxref("Reflect.defineProperty()")}}
  • 如果 target 不可扩展,那么就不能添加属性。
  • 如果并不存在一个对应于 target 的属性是不可配置的自有属性,那么就不能添加(或修改)该属性为不可配置的。
  • 如果存在一个对应于 target 的属性是可配置的,那么这个属性未必是不可配置的。
  • 如果存在一个对应于 target 的属性,那么 Object.defineProperty(target, prop, descriptor) 将不会抛出异常。
  • 在严格模式下,如果 defineProperty 处理器返回 false,则会抛出 {{jsxref("TypeError")}} 异常。
{{jsxref("Proxy/Proxy/has", "handler.has()")}}
属性查询
foo in proxy
继承属性查询
foo in Object.create(proxy)
{{jsxref("Reflect.has()")}}
  • 如果存在一个对应于 target 的属性是不可配置的自有属性,那么该属性不能被报告为不存在的。
  • 如果存在一个对应于 target 的属性是自有属性,且 target 不可扩展,那么该属性不能被报告为不存在的。
{{jsxref("Proxy/Proxy/get", "handler.get()")}}
属性访问
proxy[foo]
proxy.bar
继承属性访问
Object.create(proxy)[foo]
{{jsxref("Reflect.get()")}}
  • 如果对应于 target 的属性是不可写且不可配置的数据属性,那么该属性值必须与其相同。
  • 如果对应于 target 的属性是不可配置的访问器属性,且其 [[Get]] 属性为 undefined,那么该属性值必须为 undefined
{{jsxref("Proxy/Proxy/set", "handler.set()")}}
属性赋值
proxy[foo] = bar
proxy.foo = bar
继承属性赋值
Object.create(proxy)[foo] = bar
{{jsxref("Reflect.set()")}}
  • 如果对应于 target 的属性是不可写且不可配置的数据属性,那么就不能修改该属性的值使其不同于 target 上对应属性的值。
  • 如果对应于 target 的属性是不可配置的访问器属性,且其 [[Set]] 属性为 undefined,那么就不能设置该属性的值。
  • 在严格模式下,如果 set 处理器返回 false,则会抛出 {{jsxref("TypeError")}} 异常。
{{jsxref("Proxy/Proxy/deleteProperty", "handler.deleteProperty()")}}
属性删除
delete proxy[foo]
delete proxy.foo
{{jsxref("Reflect.deleteProperty()")}}
如果存在一个对应于 target 的属性是不可配置的自有属性,那么该属性不能被删除。
{{jsxref("Proxy/Proxy/ownKeys", "handler.ownKeys()")}} {{jsxref("Object.getOwnPropertyNames()")}}
{{jsxref("Object.getOwnPropertySymbols()")}}
{{jsxref("Object.keys()")}}
{{jsxref("Reflect.ownKeys()")}}
  • ownKeys 的返回值是一个数组。
  • 返回值中的每个元素类型为 {{jsxref("String")}} 或 {{jsxref("Symbol")}}。
  • 返回值中必须包含 target 的所有不可配置自有属性的键名。
  • 如果 target 不可扩展,那么返回值中必须有且仅有 target 的所有自有属性的键名。
{{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

可撤销的 Proxy

可以用 {{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

更好的 apply 函数

在不借助 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")}}