代理的问题与不足
- 代理是在 ECMAScript 现有的基础上构建起来的一套新 API,因此其实现已经尽力做到最好了。很大程度上,代理作为对象的虚拟层可以正常使用,但在某些情况下,代理也不能实现与现在 ECMAScript 机制很好的协同。
-
代理中的 this
代理潜在的一个问题来源是 this 的值。我们知道,方法中的 this 通常指向调用这个方法的对象:const target = { thisValEqualsproxy() { return this === proxy; }, }; const proxy = new Proxy(target, {}); console.log(target.thisValEqualsproxy()); // false console.log(proxy.thisValEqualsproxy()); // true从直觉上讲,这样完全没有问题,调用代理上的任何方法,比如 proxy.outerMethod(),而这个方法进而又会调用另一个方法,如 this.innerMethod(),实际上都会调用 proxy.innerMethod()。多数情况下,这是符合预期的行为。可是,如果目标对象依赖于对象标识,那就可能碰到意料之外的问题。
const wm = new WeakMap(); class User { constructor(userId) { wm.set(this,userId) } set id(userId) { wm.set(this, userId) } get id(userId) { return wm.get(this) } } 由于这个实现依赖User实例的对象标识,在这个实例被代理的情况下就会出问题: const user = new User(123) console.log(user.id); // 123 const userInstanceProxy = new Proxy(user, {}); console.log(userInstanceProxy.id); //undefined这是因为 User 实例一开始使用目标对象作为 WeakMap 的键,代理对象却尝试从自身取得这个实例。要解决这个问题,就需要重新配置代理,把代理 User 实例改为代理 User 类本身。之后再创建代理的实例就会以代理实例作为 WeakMap 的键了:
const UserClassProxy = new Proxy(User, {}); const proxyUser = new UserClassProxy(456); console.log(proxyUser.id); -
代理与内部槽位
- 代理与内置引用类型(比如 Array)的实例通常可以很好的协同,但有些 ECMAScript 内置类型可能会依赖代理无法控制的机制,结果导致在代理上调用某些方法会出错。
- 一个典型的例子就是 Date 类型。根据 ECMAScript 规范,Date 类型方法的执行依赖 this 值上的内部槽位[[NumberDate]]。代理对象不存在这个内部槽位,而且这个内部槽位的值也不能通过普通的 get()和 set()操作访问到,于是代理拦截后本应转发给目标对象的方法会抛出 TypeError:
const target = new Date(); const proxy = new Proxy(target, {}); console.log(proxy instanceof Date); // true proxy.getDate(); // TypeError: 'this' is not a Date object