代理
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 页面。
术语
在讨论代理的功能时会用到以下术语:
处理器和陷阱
以下表格中总结了 Proxy 对象可用的陷阱。详细的解释和例子请看参考页。
| 处理器 / 陷阱 | 拦截的操作 | 不变式 |
|---|---|---|
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()
|
|
|
handler.get()
|
|
|
handler.set()
|
|
|
handler.deleteProperty()
|
|
如果存在一个对应于 target
的属性是不可配置的自有属性,那么该属性不能被删除。
|
handler.ownKeys()
|
Object.getOwnPropertyNames()Object.getOwnPropertySymbols()Object.keys()Reflect.ownKeys()
|
|
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
}