如何扩展包装在代理中的类

JDB*_*JDB 1 javascript es6-proxy es6-class

我有一个复杂的类,需要将某些参数传递给构造函数。但是,我向客户公开了一个简化的 API。

我的内部类看起来像这样:

class Foo {
  constructor(p, c) {
  }

  foo() {
  }
}
Run Code Online (Sandbox Code Playgroud)

p客户无法方便访问的内部参考在哪里。

支持公共 API

我希望允许客户创建此类的实例,但我不希望他们需要对私有p对象的引用。对于这个 API 的使用者来说,访问p会很费力并且会破坏现有的代码,所以我想用别名来隐藏它。

子类来拯救?几乎。

起初,我只是扩展了 Foo,隐藏了私有参数(通过提供访问它的代码),并通过公共 API 公开它:

class PublicFoo extends Foo {
  constructor(c) {
    // Use internal functions to get "p"
    var p;
    super(p, c);
  }
}
Run Code Online (Sandbox Code Playgroud)

这几乎奏效了,但我遇到了一个重大缺陷。在某些情况下,客户需要测试对象的类型。根据情况,Foo 可能是使用内部类在内部创建的,也可能是由客户使用公共 API 创建的。

如果公共 API 用于创建 Foo 的实例,则内部instaceof检查工作正常:publicFoo instanceof Foo返回true。但是,如果 API 使用内部类创建了 Foo 的实例,则公共instanceof检查将失败:internalFoo instanceof PublicFoo返回false。客户可以使用公共 API 创建的类型检查实例,但对于内部创建的实例(例如,通过工厂函数),相同的类型检查会失败。

这是意料之中的,对我来说很有意义,但它破坏了我的用例。我不能使用简单的子类,因为子类不是内部类的可靠别名。

var f = new Foo();
f instanceof PublicFoo; // false
Run Code Online (Sandbox Code Playgroud)

代理呢?

所以我把“聪明”的装备提高了一个档次,并尝试使用代理代替,这似乎(著名的遗言)是一个完美的解决方案:

var PublicFoo = new Proxy(Foo, {
  construct(target, args) {
    // Use internal functions to get "p"
    var p;
    return new target(p, ...args);
  }
});
Run Code Online (Sandbox Code Playgroud)

我可以公开代理,拦截对构造函数的调用,提供必要的私有对象引用并且instanceof没有损坏!

var f = new Foo();
f instanceof PublicFoo; // true!!!
Run Code Online (Sandbox Code Playgroud)

但是代理破坏了继承......

灾难!客户不能再继承(他们的)Foo!

class Bar extends PublicFoo {
  constructor(c) {
    super(c);
  }

  bar() {
  }
}
Run Code Online (Sandbox Code Playgroud)

代理的构造函数陷阱总是返回 的新实例Foo而不是子类Bar

这会导致可怕的、可怕的问题,例如:

(new Bar()) instanceof Bar; // false!!!! 
Run Code Online (Sandbox Code Playgroud)

var b = new Bar();
b.bar() // Uncaught TypeError: b.bar is not a function
Run Code Online (Sandbox Code Playgroud)

我被困住了。

有没有办法满足以下所有条件:

  • 我的公共 API 必须隐式提供“私有”构造函数 arg(但是,由于原因™,我不能在 Foo 构造函数本身中这样做......它必须通过某种包装或拦截来完成)
  • Foo 的公共 API 版本应该是 Foo 的一个瘦别名。所以,给定f = new Foo(),那么f instanceof PublicFoo应该返回true
  • PublicFoo 应该仍然支持extends. 所以给出class Bar extends PublicFoo,那么(new Bar()) instanceof Bar应该返回true

这是我的代理难题的交互式演示:

class Foo {
  constructor(p, c) {
    this.p = p;
    this.c = c;
  }
  
  foo() {
    return "foo";
  }
}

var PublicFoo = new Proxy(Foo, {
  construct(target, args) {
    var p = "private";
    return new target(p, ...args);
  }
});

var foo = new Foo("private", "public");
console.assert( foo instanceof PublicFoo, "Foo instances are also instances of PublicFoo" );
console.assert( foo.p === "private" );
console.assert( foo.c === "public" );

var publicFoo = new PublicFoo("public");
console.assert( publicFoo instanceof Foo, "PublicFoo instances are also instances of Foo" );
console.assert( publicFoo.p === "private" );
console.assert( publicFoo.c === "public" );

class Bar extends PublicFoo {
  constructor(c) {
    super(c);
  }
  
  bar() {
    return "bar";
  }
}

var i = new Bar("public");
console.assert( i instanceof Bar, "new Bar() should return an instance of Bar" );
console.assert( i.p === "private" );
console.assert( i.c === "public" );
i.foo(); // "foo"
i.bar(); // Uncaught TypeError: i.bar is not a function
Run Code Online (Sandbox Code Playgroud)

Ber*_*rgi 5

代理呢?代理破坏了继承,因为它总是返回 的新实例Foo,而不是子类Bar

那是因为您的代理实现总是构造一个new target,而不考虑newTarget参数并将其传递给Reflect.construct

const PublicFoo = new Proxy(Foo, {
  construct(Foo, args, newTarget) {
    const p = …; // Use internal functions to get "p"
    return Reflect.construct(Foo, [p, ...args], newTarget);
  }
});
Run Code Online (Sandbox Code Playgroud)

另请参阅什么是“new.target”?.