Proxy
Proxy对象提供了JS可以在更高层面操控对象的可能,它没有自己的状态和行为,只负责将外界的操作传递给原始对象,本质是代理模式。
对于每个可被 Proxy 捕获的内部方法,在 Reflect 中都有一个对应的方法,其名称和参数与 Proxy 捕捉器相同。
拦截外界的操作遵循以下步骤:
- 首先查询handlers中是否存在相应的handler,如果有,则调用它操作原始对象
- 反之,则将操作传递给原始对象。
Proxy 的局限性
许多内建对象,例如 Map,Set,Date,Promise 等,都使用了所谓的“内部插槽”。
它们类似于属性,但仅限于内部使用,仅用于规范目的。例如,Map 将项目(item)存储在 [[MapData]] 中。内建方法可以直接访问它们,而不通过 [[Get]]/[[Set]] 内部方法。所以 Proxy 无法拦截它们。
例如:
let map = new Map();
let proxy = new Proxy(map, {});
proxy.set('test', 1); // TypeError: Method Map.prototype.set called on incompatible receiver #<Map>可以如此解决:
let map = new Map();
let proxy = new Proxy(map, {
get(target, p, receiver) {
let value = Reflect.get(...arguments);
return typeof value === 'function' ? value.bind(target) : value;
},
});
proxy.set('test', 1);WARNING
注意:handlers中劫持的是get方法,调用时是set方法
理论上说,Map对象和一般对象并没有区别。从外部看,map.get('test') 是两个基本语义的组合:
- map_get = Get(map, 'get')
- Apply(map_get, map, ['test'])
落实到具体的Map对象上,第一步默认情况下是获得Map.prototype.get方法, 第二步默认相当于执行Map.prototype.get.apply(map, ['test'])。
真正调用内部槽的是Map.prototype.get方法的内部实现,它会存取发送给它的this参数(map对象)上的[[MapData]]内部槽(因此,假若你传入的this参数不是Map的实例对象从而没有该内部槽,就会扔TypeError)。
在该例中,由于proxy对象上没有对应的内部插槽,导致在执行Map.prototype.get时无法访问到而抛出TypeError
存取内部槽总是一个内部实现细节,从对象的使用者角度说,是无从知晓的。
当然,虽说理论上内部槽是不可观测的,但实践上我们可以近似判断:一个存取了内部槽的方法是不能简单地代理调用的。
假设有对象o,let p = new Proxy(o, {})
- 对于普通对象,p.method()和o.method()结果通常是一致的,此即所谓代理透明性。
- 但如果method访问了o的内部槽,由于p(代理对象)并没有o的那些内部槽 ,故而会抛TypeError。
注意,我们只能猜测o有内部槽而不可能严格断言,因为方法可能由于任何原因抛TypeError,或者访问内部槽的代码可以通过测试是否具有内部槽或catch掉error来不抛TypeError。
WARNING
使用Proxy代理有内部插槽(internal slot)的对象时,会出现古怪的现象,因为内部插槽对Proxy来说是不透明的。
可撤销的Proxy
Proxy提供了一个可撤销代理的方法:Proxy.revocable()。它返回一个对象,包含两个属性,分别是:proxy、revoke
- proxy:代理原始对象的proxy对象
- revoke:撤销按钮
function sayHello() {
console.log(123);
}
const { proxy, revoke } = Proxy.revocable(sayHello, {});
proxy(); // 123
revoke();
proxy(); // TypeError: Cannot perform 'apply' on a proxy that has been revokedProxy.revocable()的使用场景多为:当你要使用不信任的第三方依赖时需要将向它传递一个函数,那么可以执行proxy之后再执行revoke撤销代理。多用于防止第三方劫持你函数的引用,是防御编程的一种。
proxy invariants
虽然通过Proxy对象可以精细化管理引用数据类型的操作,但是不能定义明显错误的行为。Proxy类会对结果进行检查,防止出现违背JS invariant的行为
let target = Object.freeze({ x: 1 });
let proxy = new Proxy(target, {
get(target, p, receiver) {
return 99;
},
});
console.log(proxy.x);
// TypeError: 'get' on proxy: property 'x' is a read-only and non-configurable data property on the proxy target but the proxy did not return its actual value (expected '1' but got '99')参考:
【2】Proxy

