ECMAScript 6 新增了 Proxy
与 Reflect
,让JavaScript也可以实现元编程。Proxy
代理类通过包含捕获器的 ProxyHandler
对象来拦截目标对象的基本操作和方法,来改变目标对象原本的逻辑。而 Reflect
通过其 API
可以操作JavaScript对象。
创建代理
Proxy
类提供了构造器来创建 Proxy
对象。
Proxy(target, handler)
其中的 target
参数为要拦截的目标对象,handler
参数为处理程序对象。当缺少任何一个参数都会抛出 TypeError
。
let target = {
name: "小王",
age: 20,
id: 2021
};
let proxy = new Proxy(target, {
get: function() {
return "ProxyHandler.get()";
}
});
proxy.name // ProxyHandler.get()
proxy.id // ProxyHandler.get()
由上可知,handler
对象中定义了 get
捕获器来对目标对象中的读取操作进行捕获拦截,从而修改读取操作的行为。只有发生在代理对象上,才会触发捕获器,在目标对象上执行仍会产生正常行为。
如想要创建一个空代理,可以在创建 Proxy
对象时, handler
参数传入的值为 {}
即可。
let target = {};
let proxy = new Proxy(target, {});
捕获器
在 handler
中可以包含零个或多个捕获器,可直接或间接在代理对象上调用。handler
参数的类型为 ProxyHandler
类,除了上面介绍的 get()
方法外,还包含了其他的捕获器,如 getPrototypeOf()
、has()
、 apply()
等 13 个捕获器。
has
has
捕获器会对 in
运算符劫持,用于判断某个属性是否存在。
let target = {
name: "小王",
age: 20,
id: 2021
};
let proxy = new Proxy(target, {
has(target, p) {
if (p.startsWith('a'))
return false;
return p in target;
}
});
'name' in proxy // true
'age' in proxy // false
'id' in proxy // true
捕获器参数
在使用捕获器时,可以访问相应的参数来重建被捕获方法的原始行为。如上面的例子中的 has()
捕获器会接收到目标对象、要查询的属性。可以通过调用封装了原始行为的 Reflect
对象上的方法来重建,几乎 ProxyHandler
中的捕获器在 Reflect
中,都能找到与之相对应的方法 。
let proxy = new Proxy(target, {
has(target, p) {
if (p.startsWith('a'))
return false;
return Reflect.has(...arguments);
}
});
当然,如果创建的代理对象中,不对目标对象进行修改,但需要捕获所有方法,可以直接传 Reflect
。
let target = {
name: "小王",
age: 20,
id: 2021
};
let proxy = new Proxy(target, Reflect);
'age' in target // true
'age' in proxy // true
可撤销代理
使用 new Proxy()
来创建的代理,与目标对象之间的代理关系,会在其生命周期内一直存在。但有时也需要撤销代理对象与目标对象之间的联系,这时就可以使用 Proxy
提供的 revocable()
方法来创建一个支持可撤销的代理对象。
let target = {
name: "小王"
};
const { proxy, revoke } = Proxy.revocable(target, {
get() {
return "小信";
}
});
console.log(target.name); // 小王
console.log(proxy.name); // 小信
revoke();
console.log(target.name); // 小王
console.log(proxy.name); // TypeError: Cannot perform 'get' on a proxy that has been revoked
这里调用 revoke()
函数是幂等的,指的是无论调用多少次,其结果不变。当撤销后,在调用代理时,会抛出 TypeError
异常,如上所示。
反射 API
上面也介绍了 ProxyHandler
提供了 13 个方法来捕获目标对象中的操作,但若需要在捕获器中调用对象的默认行为,就可以使用 Reflect
提供的 API
,其提供的 API
基本都有捕获器相对应。下面就介绍 apply
和 defineProperty
两个方法。
apply
Reflect
中提供了 apply
方法通过指定的参数列表发起目标函数的调用,和 Function.prototype.apply()
功能类似。
Reflect.apply(Math.min, Math, args); => Math.min.apply(Math, args);
当 Proxy
对象中的 ProxyHandler
使用 apply
捕获器来拦截函数调用时,可以使用 Reflect.apply()
来调用目标对象中函数的默认行为。
let target = function (a, b) {
return a + b;
}
let proxy = new Proxy(target, {
apply(target, thisArg, argArray) {
return Reflect.apply(...arguments) * 2;
}
});
target(1, 2); // 3
proxy.apply(null, [1, 2]); // 6
defineProperty
Reflect
提供的 defineProperty
方法与 Object.defineProperty()
基本相同,不同的是 Reflect
的方法返回的是 Boolean
值,该方法主要用于添加或修改对象上的属性。
let target = {
name: "小王",
age: 20,
id: 2021
};
target.address; // undefined
Reflect.defineProperty(target, "address", {value: "新疆"});
target.address; // 新疆
总结
ECMAScript 6 新增的 Proxy
可以对对象提供了基本操作的拦截和改变原本的逻辑,从而丰富了开发的灵活性。而 Reflect
提供的 API
可以对绝大部分对象进行操作,如 Reflect.getPrototypeOf()
类似于 Object.getPrototypeOf()
。应用场景也非常大,如跟踪属性访问、隐藏属性、参数验证、数据绑定以及观察对象等。

原文始发于微信公众号(海人为记):快速了解ES6的代理与反射
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/27617.html