Skip to content

Proxy的局限性

内置对象

代理提供了一种特殊的方法,可以在最底层更改或调整现有对象的行为。但是,它并不完美也有局限性。 局限性在于对内置对象的处理,如Map,Set,Date,Promise,Class的私有属性,这些都使用了所谓的“内部插槽”。

它们类似于属性,但仅限于内部使用,仅用于规范目的。例如,Map 将项目(item)存储在 [[MapData]] 中。内建方法可以直接访问它们,而不通过 [[Get]]/[[Set]] 内部方法。所以 Proxy 无法拦截它们。

在类似这样的内建对象被代理后,代理对象没有这些内部插槽,因此内建方法将会失败。

报错的实例:

javascript
const map = new Map();
const proxy = new Proxy(map, {
  get: (target, prop) => {
    console.log(`Getting ${prop}`);
    return target[prop];
  }
});

proxy.set('name', 'hyw');

报错:

shell
TypeError: Method Map.prototype.set called on incompatible receiver #<Map>

在内部,一个 Map 将所有数据存储在其 [[MapData]] 内部插槽中。代理对象没有这样的插槽。内建方法 Map.prototype.set 方法试图访问内部属性 this.[[MapData]],但由于 this=proxy,在 proxy 中无法找到,所以失败了。

一种解决方法:

javascript
let proxy = new Proxy(map, {
  get(target, prop, receiver) {
    let value = Reflect.get(...arguments);
    return typeof value == "function" ? value.bind(target) : value;
  },
});

proxy.set("name", "hyw");
console.log(proxy.get("name"));//输出hyw

arguments为"get"/"set"(注意,这里只是捕捉器名称),Reflect获得语言内部的方法,value即为get/set方法,并绑定到了目标对象(map)本身。所以proxy.set方法操作的不是代理对象。当set 捕捉器的内部实现尝试访问 this.[[MapData]] 内部插槽时,所以会成功。

对象的私有属性

javascript
class User {
  #name = "hyw";
  getName() {
    return this.#name;
  }
}
let user = new User();
user = new Proxy(user, {});
console.log(user.getName());

会有如下报错

shell
Cannot read private member #name from an object whose class did not declare it

修复的办法和内置对象保持一致。

可以撤销的proxy

一个代理对象,如果在特定情况下不再允许被代理,在声明时需要声明为Proxy.revocable。

javascript
let object = {
  data: "Valuable data",
};

let { proxy, revoke } = Proxy.revocable(object, {});
console.log(proxy.data); // Valuable data

//撤销
revoke();
console.log(proxy.data); // Error

陕ICP备15010740号