元编程 - JavaScript 编辑
从ECMAScript 2015 开始,JavaScript 获得了 Proxy
和 Reflect
对象的支持,允许你拦截并定义基本语言操作的自定义行为(例如,属性查找,赋值,枚举,函数调用等)。借助这两个对象,你可以在 JavaScript 元级别进行编程。
代理
在 ECMAScript 6 中引入的 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。
更多例子参见 Proxy
页面 。
术语
在讨论代理的功能时会用到以下术语。
handler
- 包含陷阱的占位符对象。
- traps
- 提供属性访问的方法。这类似于操作系统中陷阱的概念。
- target
- 代理虚拟化的对象。它通常用作代理的存储后端。根据目标验证关于对象不可扩展性或不可配置属性的不变量(保持不变的语义)。
- invariants
- 实现自定义操作时保持不变的语义称为不变量。如果你违反处理程序的不变量,则会抛出一个
TypeError
。
句柄和陷阱
以下表格中总结了 Proxy
对象可用的陷阱。详细的解释和例子请看参考页。
Handler / trap | Interceptions | Invariants |
---|---|---|
handler.getPrototypeOf() | Object.getPrototypeOf() Reflect.getPrototypeOf() __proto__ Object.prototype.isPrototypeOf() instanceof |
|
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() |
|
handler.defineProperty() | Object.defineProperty() Reflect.defineProperty() |
|
handler.has() | Property query: foo in proxy Inherited property query: foo in Object.create(proxy) Reflect.has() |
|
handler.get() | Property access: proxy[foo] and proxy.bar Inherited property access: Object.create(proxy)[foo] Reflect.get() |
|
handler.set() | Property assignment: proxy[foo] = bar and proxy.foo = bar Inherited property assignment: Object.create(proxy)[foo] = bar Reflect.set() |
|
handler.deleteProperty() | Property deletion: delete proxy[foo] and delete proxy.foo Reflect.deleteProperty() | A property cannot be deleted, if it exists as a non-configurable own property of the target object. |
handler.enumerate() | Property enumeration / for...in: for (var name in proxy) {...} Reflect.enumerate() | The enumerate method must return an object. |
handler.ownKeys() | Object.getOwnPropertyNames() Object.getOwnPropertySymbols() Object.keys() Reflect.ownKeys() |
|
handler.apply() | proxy(..args) Function.prototype.apply() and Function.prototype.call() Reflect.apply() | There are no invariants for the handler.apply method. |
handler.construct() | new proxy(...args) Reflect.construct() | 结果一定是一个Object 。 |
撤销 Proxy
Proxy.revocable()
方法被用来创建可撤销的 Proxy
对象。这意味着 proxy 可以通过 revoke
函数来撤销,并且关闭代理。此后,代理上的任意的操作都会导致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
反射
Reflect
是一个内置对象,它提供了可拦截 JavaScript 操作的方法。该方法和代理句柄
类似,但 Reflect
方法并不是一个函数对象。
Reflect
有助于将默认操作从处理程序转发到目标。
以 Reflect.has()
为例,你可以将 in
运算符作为函数:
Reflect.has(Object, "assign"); // true
更好的 apply 函数
在 ES5 中,我们通常使用 Function.prototype.apply()
方法调用一个具有给定 this
值和 arguments
数组(或类数组对象)的函数。
Function.prototype.apply.call(Math.floor, undefined, [1.75]);
使用 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"
检查属性定义是否成功
使用 Object.defineProperty
,如果成功返回一个对象,否则抛出一个 TypeError
,你将使用 try...catch
块来捕获定义属性时发生的任何错误。因为 Reflect.defineProperty
返回一个布尔值表示的成功状态,你可以在这里使用 if...else
块:
if (Reflect.defineProperty(target, property, attributes)) {
// success
} else {
// failure
}
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论