--- title: 元编程 slug: Web/JavaScript/Guide/Meta_programming tags: - Guide - JavaScript - Proxy - Reflect translation_of: Web/JavaScript/Guide/Meta_programming ---
{{jsSidebar("JavaScript Guide")}} {{PreviousNext("Web/JavaScript/Guide/Iterators_and_Generators", "Web/JavaScript/Guide/Modules")}}

从ECMAScript 2015 开始,JavaScript 获得了 {{jsxref("Proxy")}} 和 {{jsxref("Reflect")}} 对象的支持,允许你拦截并定义基本语言操作的自定义行为(例如,属性查找,赋值,枚举,函数调用等)。借助这两个对象,你可以在 JavaScript 元级别进行编程。

代理

在 ECMAScript 6 中引入的 {{jsxref("Proxy")}} 对象可以拦截某些操作并实现自定义行为。例如获取一个对象上的属性:

let handler = {
  get: function(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 对象定义了一个目标(这里是一个空对象)和一个实现了 get 陷阱的 handler 对象。这里,代理的对象在获取未定义的属性时不会返回 undefined,而是返回 42。

更多例子参见 {{jsxref("Proxy")}} 页面 。

术语

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

{{jsxref("Global_Objects/Proxy/handler","handler")}}
包含陷阱的占位符对象。
traps
提供属性访问的方法。这类似于操作系统中陷阱的概念。
target
代理虚拟化的对象。它通常用作代理的存储后端。根据目标验证关于对象不可扩展性或不可配置属性的不变量(保持不变的语义)。
invariants
实现自定义操作时保持不变的语义称为不变量。如果你违反处理程序的不变量,则会抛出一个 {{jsxref("TypeError")}}。

句柄和陷阱

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

Handler / trap Interceptions Invariants
{{jsxref("Global_Objects/Proxy/handler/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("Global_Objects/Proxy/handler/setPrototypeOf", "handler.setPrototypeOf()")}} {{jsxref("Object.setPrototypeOf()")}}
{{jsxref("Reflect.setPrototypeOf()")}}
如果 target 不可扩展,prototype 参数必须与Object.getPrototypeOf(target)的值相同。
{{jsxref("Global_Objects/Proxy/handler/isExtensible", "handler.isExtensible()")}} {{jsxref("Object.isExtensible()")}}
{{jsxref("Reflect.isExtensible()")}}
Object.isExtensible(proxy) 必须返回和Object.isExtensible(target)一样的值。
{{jsxref("Global_Objects/Proxy/handler/preventExtensions", "handler.preventExtensions()")}} {{jsxref("Object.preventExtensions()")}}
{{jsxref("Reflect.preventExtensions()")}}
 如果Object.isExtensible(proxy) 值为 false,Object.preventExtensions(proxy) 只返回true。
{{jsxref("Global_Objects/Proxy/handler/getOwnPropertyDescriptor", "handler.getOwnPropertyDescriptor()")}} {{jsxref("Object.getOwnPropertyDescriptor()")}}
{{jsxref("Reflect.getOwnPropertyDescriptor()")}}
  • getOwnPropertyDescripton 只能返回对象或者undefined.
  • A property cannot be reported as non-existent, if it exists as a non-configurable own property of the target object.
  • A property cannot be reported as non-existent, if it exists as an own property of the target object and the target object is not extensible.
  • A property cannot be reported as existent, if it does not exists as an own property of the target object and the target object is not extensible.
  • A property cannot be reported as non-configurable, if it does not exists as an own property of the target object or if it exists as a configurable own property of the target object.
  • The result of Object.getOwnPropertyDescriptor(target) can be applied to the target object using Object.defineProperty and will not throw an exception.
{{jsxref("Global_Objects/Proxy/handler/defineProperty", "handler.defineProperty()")}} {{jsxref("Object.defineProperty()")}}
{{jsxref("Reflect.defineProperty()")}}
  • A property cannot be added, if the target object is not extensible.
  • A property cannot be added as or modified to be non-configurable, if it does not exists as a non-configurable own property of the target object.
  • A property may not be non-configurable, if a corresponding configurable property of the target object exists.
  • If a property has a corresponding target object property then Object.defineProperty(target, prop, descriptor) will not throw an exception.
  • In strict mode, a false return value from the defineProperty handler will throw a {{jsxref("TypeError")}} exception.
{{jsxref("Global_Objects/Proxy/handler/has", "handler.has()")}} Property query: foo in proxy
Inherited property query: foo in Object.create(proxy)
{{jsxref("Reflect.has()")}}
  • A property cannot be reported as non-existent, if it exists as a non-configurable own property of the target object.
  • A property cannot be reported as non-existent, if it exists as an own property of the target object and the target object is not extensible.
{{jsxref("Global_Objects/Proxy/handler/get", "handler.get()")}} Property access: proxy[foo]and proxy.bar
Inherited property access: Object.create(proxy)[foo]
{{jsxref("Reflect.get()")}}
  • The value reported for a property must be the same as the value of the corresponding target object property if the target object property is a non-writable, non-configurable data property.
  • The value reported for a property must be undefined if the corresponding target object property is non-configurable accessor property that has undefined as its [[Get]] attribute.
{{jsxref("Global_Objects/Proxy/handler/set", "handler.set()")}} Property assignment: proxy[foo] = bar and proxy.foo = bar
Inherited property assignment: Object.create(proxy)[foo] = bar
{{jsxref("Reflect.set()")}}
  • Cannot change the value of a property to be different from the value of the corresponding target object property if the corresponding target object property is a non-writable, non-configurable data property.
  • Cannot set the value of a property if the corresponding target object property is a non-configurable accessor property that has undefined as its [[Set]] attribute.
  • In strict mode, a false return value from the set handler will throw a {{jsxref("TypeError")}} exception.
{{jsxref("Global_Objects/Proxy/handler/deleteProperty", "handler.deleteProperty()")}} Property deletion: delete proxy[foo] and delete proxy.foo
{{jsxref("Reflect.deleteProperty()")}}
A property cannot be deleted, if it exists as a non-configurable own property of the target object.
{{jsxref("Global_Objects/Proxy/handler/enumerate", "handler.enumerate()")}} Property enumeration / for...in: for (var name in proxy) {...}
{{jsxref("Reflect.enumerate()")}}
The enumerate method must return an object.
{{jsxref("Global_Objects/Proxy/handler/ownKeys", "handler.ownKeys()")}} {{jsxref("Object.getOwnPropertyNames()")}}
{{jsxref("Object.getOwnPropertySymbols()")}}
{{jsxref("Object.keys()")}}
{{jsxref("Reflect.ownKeys()")}}
  • The result of ownKeys is a List.
  • The Type of each result List element is either {{jsxref("String")}} or {{jsxref("Symbol")}}.
  • The result List must contain the keys of all non-configurable own properties of the target object.
  • If the target object is not extensible, then the result List must contain all the keys of the own properties of the target object and no other values.
{{jsxref("Global_Objects/Proxy/handler/apply", "handler.apply()")}} proxy(..args)
{{jsxref("Function.prototype.apply()")}} and {{jsxref("Function.prototype.call()")}}
{{jsxref("Reflect.apply()")}}
There are no invariants for the handler.apply method.
{{jsxref("Global_Objects/Proxy/handler/construct", "handler.construct()")}} new proxy(...args)
{{jsxref("Reflect.construct()")}}
结果一定是一个Object

撤销 Proxy

{{jsxref("Proxy.revocable()")}} 方法被用来创建可撤销的 Proxy 对象。这意味着 proxy 可以通过 revoke 函数来撤销,并且关闭代理。此后,代理上的任意的操作都会导致{{jsxref("TypeError")}}。

var revocable = Proxy.revocable({}, {
  get: function(target, name) {
    return "[[" + name + "]]";
  }
});
var proxy = revocable.proxy;
console.log(proxy.foo); // "[[foo]]"

revocable.revoke();

console.log(proxy.foo); // TypeError is thrown
proxy.foo = 1           // TypeError again
delete proxy.foo;       // still TypeError
typeof proxy            // "object", typeof doesn't trigger any trap

反射

{{jsxref("Reflect")}} 是一个内置对象,它提供了可拦截 JavaScript 操作的方法。该方法和{{jsxref("Global_Objects/Proxy/handler","代理句柄")}}类似,但 Reflect 方法并不是一个函数对象。

Reflect 有助于将默认操作从处理程序转发到目标。

以 {{jsxref("Reflect.has()")}} 为例,你可以将 in 运算符作为函数:

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

更好的 apply 函数

在 ES5 中,我们通常使用 {{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
}

{{Previous("Web/JavaScript/Guide/Iterators_and_Generators")}}