如何扩展包裹在代理中的 class
How to extend a class wrapped in a Proxy
我有一个复杂的 class 需要将某些参数传递给构造函数。但是,我向客户公开了一个简化的 API。
我的内部 class 看起来像这样:
class Foo {
constructor(p, c) {
}
foo() {
}
}
其中 p
是客户不方便访问的内部参考。
支持 Public API
我想让客户创建此 class 的实例,但我不希望他们需要对私有 p
对象的引用。对于这个 API 的消费者,访问 p
会很费力并且会破坏现有代码,所以我想用别名隐藏它。
子class有救了?差不多。
起初我简单地扩展了 Foo,隐藏了私有参数(通过提供访问它的代码),然后通过 public API:
暴露了它
class PublicFoo extends Foo {
constructor(c) {
// Use internal functions to get "p"
var p;
super(p, c);
}
}
这几乎成功了,但我 运行 陷入了一个重大缺陷。在某些情况下,客户需要测试对象的类型。根据情况,Foo 可能使用内部 class 在内部创建,或者由客户使用 public API.
如果 public API 用于创建 Foo 的实例,那么内部 instaceof
检查工作正常: publicFoo instanceof Foo
returns true
。但是,如果 API 使用内部 class 创建了一个 Foo 实例,那么 public instanceof
检查失败: internalFoo instanceof PublicFoo
returns false
。客户可以对使用 public API 创建的实例进行类型检查,但对于内部创建的实例(例如,通过工厂函数),相同类型的检查会失败。
这是意料之中的,对我来说很有意义,但它打破了我的用例。我不能使用简单的 sub-class 因为 sub-class 不是内部 class.
的可靠别名
var f = new Foo();
f instanceof PublicFoo; // false
代理呢?
所以我将“聪明”的装备提高了一个档次并尝试使用代理代替,似乎(著名的遗言) 求完美解决:
var PublicFoo = new Proxy(Foo, {
construct(target, args) {
// Use internal functions to get "p"
var p;
return new target(p, ...args);
}
});
我可以公开代理、拦截对构造函数的调用、提供必要的私有对象引用并且 instanceof
没有损坏!
var f = new Foo();
f instanceof PublicFoo; // true!!!
但是 Proxy 破坏了继承...
灾难!客户不能再继承自(他们的)Foo!
class Bar extends PublicFoo {
constructor(c) {
super(c);
}
bar() {
}
}
Proxy 的构造函数陷阱总是 return 是 Foo
的新实例,不是子class Bar
。
这会导致非常可怕的问题,例如:
(new Bar()) instanceof Bar; // false!!!!
和
var b = new Bar();
b.bar() // Uncaught TypeError: b.bar is not a function
我卡住了。
有没有办法满足以下所有条件:
- 我的 public API 必须隐式提供“私有”构造函数 arg(但是,出于原因™,我不能在Foo 构造函数本身......它必须通过包装器或某种拦截来完成)
- public API 版本的 Foo 应该是 Foo 的精简别名。所以,给定
f = new Foo()
,那么 f instanceof PublicFoo
应该 return true
- PublicFoo 应该仍然支持
extends
。所以给定 class Bar extends PublicFoo
,那么 (new Bar()) instanceof Bar
应该 return 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
我建议不要使用子类化,或者使用代理尝试一些巧妙的方法。为什么不直接做
class Foo {
// private API - people can try but they won't come up with a valid `p`
constructor(p, c) {
}
// public API
static create(c, ...args) {
const p = …; // Use internal functions to get "p"
return new this(p, c, ...args);
}
// public API
foo() {
}
}
当然,人们不会使用 new Foo
,而是 Foo.create()
,他们的子类将不得不通过内部 p
.
如果你绝对需要支持new PublicFoo
语法,我会推荐
function PublicFoo(c) {
const p = …; // Use internal functions to get "p"
return new Foo(p, c, ...args);
}
PublicFoo.prototype = Foo.prototype;
(可能 Foo.prototype.constructor = PublicFoo
,如果这对您很重要的话)。这种模式仍然很好地支持 ES6 继承(class extends PublicFoo
),关于 instanceof
,PublicFoo
和 Foo
是完全等价的。
有关详细信息,另请参阅 this answer。
What about a Proxy? A proxy breaks inheritance as it always returns a new instance of Foo
, not the subclass Bar
.
那是因为您的代理实现总是构造一个 new target
,而不考虑 newTarget
parameter 并将其传递给 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);
}
});
另见 。
我有一个复杂的 class 需要将某些参数传递给构造函数。但是,我向客户公开了一个简化的 API。
我的内部 class 看起来像这样:
class Foo {
constructor(p, c) {
}
foo() {
}
}
其中 p
是客户不方便访问的内部参考。
支持 Public API
我想让客户创建此 class 的实例,但我不希望他们需要对私有 p
对象的引用。对于这个 API 的消费者,访问 p
会很费力并且会破坏现有代码,所以我想用别名隐藏它。
子class有救了?差不多。
起初我简单地扩展了 Foo,隐藏了私有参数(通过提供访问它的代码),然后通过 public API:
暴露了它class PublicFoo extends Foo {
constructor(c) {
// Use internal functions to get "p"
var p;
super(p, c);
}
}
这几乎成功了,但我 运行 陷入了一个重大缺陷。在某些情况下,客户需要测试对象的类型。根据情况,Foo 可能使用内部 class 在内部创建,或者由客户使用 public API.
如果 public API 用于创建 Foo 的实例,那么内部 instaceof
检查工作正常: publicFoo instanceof Foo
returns true
。但是,如果 API 使用内部 class 创建了一个 Foo 实例,那么 public instanceof
检查失败: internalFoo instanceof PublicFoo
returns false
。客户可以对使用 public API 创建的实例进行类型检查,但对于内部创建的实例(例如,通过工厂函数),相同类型的检查会失败。
这是意料之中的,对我来说很有意义,但它打破了我的用例。我不能使用简单的 sub-class 因为 sub-class 不是内部 class.
的可靠别名var f = new Foo();
f instanceof PublicFoo; // false
代理呢?
所以我将“聪明”的装备提高了一个档次并尝试使用代理代替,似乎(著名的遗言) 求完美解决:
var PublicFoo = new Proxy(Foo, {
construct(target, args) {
// Use internal functions to get "p"
var p;
return new target(p, ...args);
}
});
我可以公开代理、拦截对构造函数的调用、提供必要的私有对象引用并且 instanceof
没有损坏!
var f = new Foo();
f instanceof PublicFoo; // true!!!
但是 Proxy 破坏了继承...
灾难!客户不能再继承自(他们的)Foo!
class Bar extends PublicFoo {
constructor(c) {
super(c);
}
bar() {
}
}
Proxy 的构造函数陷阱总是 return 是 Foo
的新实例,不是子class Bar
。
这会导致非常可怕的问题,例如:
(new Bar()) instanceof Bar; // false!!!!
和
var b = new Bar();
b.bar() // Uncaught TypeError: b.bar is not a function
我卡住了。
有没有办法满足以下所有条件:
- 我的 public API 必须隐式提供“私有”构造函数 arg(但是,出于原因™,我不能在Foo 构造函数本身......它必须通过包装器或某种拦截来完成)
- public API 版本的 Foo 应该是 Foo 的精简别名。所以,给定
f = new Foo()
,那么f instanceof PublicFoo
应该 returntrue
- PublicFoo 应该仍然支持
extends
。所以给定class Bar extends PublicFoo
,那么(new Bar()) instanceof Bar
应该 returntrue
.
这是我的代理难题的交互式演示:
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
我建议不要使用子类化,或者使用代理尝试一些巧妙的方法。为什么不直接做
class Foo {
// private API - people can try but they won't come up with a valid `p`
constructor(p, c) {
}
// public API
static create(c, ...args) {
const p = …; // Use internal functions to get "p"
return new this(p, c, ...args);
}
// public API
foo() {
}
}
当然,人们不会使用 new Foo
,而是 Foo.create()
,他们的子类将不得不通过内部 p
.
如果你绝对需要支持new PublicFoo
语法,我会推荐
function PublicFoo(c) {
const p = …; // Use internal functions to get "p"
return new Foo(p, c, ...args);
}
PublicFoo.prototype = Foo.prototype;
(可能 Foo.prototype.constructor = PublicFoo
,如果这对您很重要的话)。这种模式仍然很好地支持 ES6 继承(class extends PublicFoo
),关于 instanceof
,PublicFoo
和 Foo
是完全等价的。
有关详细信息,另请参阅 this answer。
What about a Proxy? A proxy breaks inheritance as it always returns a new instance of
Foo
, not the subclassBar
.
那是因为您的代理实现总是构造一个 new target
,而不考虑 newTarget
parameter 并将其传递给 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);
}
});
另见