代理的问题与不足

210 阅读2分钟

代理的问题与不足

  • 代理是在 ECMAScript 现有的基础上构建起来的一套新 API,因此其实现已经尽力做到最好了。很大程度上,代理作为对象的虚拟层可以正常使用,但在某些情况下,代理也不能实现与现在 ECMAScript 机制很好的协同。
  1. 代理中的 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);
    
  2. 代理与内部槽位

    • 代理与内置引用类型(比如 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