如何在TypeScript中建立动态原型链?

如何在TypeScript中建立动态原型链?,typescript,prototypal-inheritance,Typescript,Prototypal Inheritance,在JavaScript中,我可以编写一个“派生类”,其“基类”是动态的,代码如下: 函数类(sF){ 函数DynamicBaseClass(iF){this.instanceField=iF;} //编辑:哎呀,从ES6的意义上讲,这不是真正的静态,但它是在 //“基础”原型,重要的是,不在最终对象中。 DynamicBaseClass.prototype.staticField=sF; 返回DynamicBaseClass; } 函数NewDerivedClass(基类){ 函数派生类(iF、

在JavaScript中,我可以编写一个“派生类”,其“基类”是动态的,代码如下:

函数类(sF){
函数DynamicBaseClass(iF){this.instanceField=iF;}
//编辑:哎呀,从ES6的意义上讲,这不是真正的静态,但它是在
//“基础”原型,重要的是,不在最终对象中。
DynamicBaseClass.prototype.staticField=sF;
返回DynamicBaseClass;
}
函数NewDerivedClass(基类){
函数派生类(iF、dF){
基类调用(this,iF);
this.derivedField=dF;
}
DerivedClass.prototype=Object.create(baseClass.prototype);
Object.defineProperty(DerivedClass.prototype,'构造函数',{
值:DerivedClass,
可枚举:false,//从“for in”循环中忽略
可写:对
});
DerivedClass.prototype.dump=函数dump(){
console.log(“instanceField=“+this.instanceField+
“derivedField=“+this.derivedField+
“staticField=“+this.staticField+
“base=“+this.\uuuuu proto\uuuuu.\uuuuuu proto\uuuuuu.constructor.name”);
}
返回派生类;
}
var BaseClass 1=NewBaseClass(“动态原型1”);
var BaseClass2=NewBaseClass(“动态原型2”);
new(NewDerivedClass(BaseClass1))(3,33.dump();
new(NewDerivedClass(BaseClass1))(4,44.dump();
new(NewDerivedClass(BaseClass2))(5,55.dump();
new(NewDerivedClass(BaseClass2))(6,66.dump();
//输出:
//instanceField=3 derivedField=33 staticField=DynamicPrototype#1 base=DynamicBaseClass
//instanceField=4 derivedField=44 staticField=DynamicPrototype#1 base=DynamicBaseClass
//instanceField=5 derivedField=55 staticField=DynamicPrototype#2 base=DynamicBaseClass
//instanceField=6 derivedField=66 staticField=DynamicPrototype#2 base=DynamicBaseClass

(这样做有多种原因;在我的例子中,这样做可以节省内存:如果一大群对象都需要几个属性的相同值,它们可以在原型链中高效地共享这些公共值。)

如何在TypeScript中实现类似的效果?如果用于定义类的代码有点难看,也可以,只要可以编写
new(NewDerivedClass(BaseClass))
以使该表达式具有合理的类型。

一般解决方案 以下代码在TypeScript中产生相同的效果(
staticField
在原型链上,而不是在派生对象上)。但是,请注意,在基类中使用真正的
静态
字段更容易:您不需要在
NewBaseClass
中编写
作为基类

  • TypeScript 3.8.3并不完全接受它:它抱怨
    DerivedClass
    ,说“一个mixin类必须有一个构造函数,其中包含一个类型为“any[]”的rest参数”。但是,可以使用
    /@ts ignore
    抑制此错误
  • TypeScript 3.6.5似乎不理解
    baseClass
    是非空的,因此给出了多个错误。它还说“导出函数的返回类型具有或正在使用私有名称‘DerivedClass’”,这很奇怪,因为
    NewDerivedClass
    没有导出。后一个错误的解决方法是定义匹配接口并将其用作返回类型:

    interface DerivedClass_ {
      new (iF: number, dF: number): {
        derivedField: number;
        dump(): void;
      }
    }
    
我看到它使用了
Object.setPrototypeOf
,MDN警告我们出于性能原因不要使用它。我希望打字的人知道他们在做什么

“廉价”数据共享技术 如果目标只是在多个实例之间共享数据,而不消耗单个实例上的任何内存,那么可以这样做:

interface DynamicClass_ { // not needed in TypeScript 3.8
  new (iF: number, dF: number): {
    instanceField: number;
    derivedField: number;
  };
}
function NewClass(staticField: string, foo: any): DynamicClass_ {
  class DynamicClass {
    constructor(public instanceField: number, 
                public derivedField: number) { }
    dump() {
      console.log("instanceField=" + this.instanceField +
          " derivedField=" + this.derivedField + 
          " staticField=" + staticField + // <<<<<<<<<<<<<<<<<<<<<<<<<
          " foo=" + foo);                 // <<<<<<<<<<<<<<<<<<<<<<<<<
    }
  }
  return DynamicClass;
}
ES2015课程 因为
extends
关键字可以接受一个变量,所以可以将函数类构造函数转换为ES2015样式的类

函数NewBaseClass(sF:string){
返回舱{
静态场=sF;
instanceField:编号;
构造函数(如果:编号){
this.instanceField=iF;
}
};
}
接口超类类型{
新增(如果:编号):{
instanceField:编号;
静态字段:字符串;
};
}
函数NewDerivedClass(基类:超类类型){
返回类扩展了基类{
derivedField:数字;
构造函数(iF:number,dF:number){
超级(如果有);
this.derivedField=dF;
}
转储(){
console.log(“instanceField=“+this.instanceField+
“derivedField=“+this.derivedField+
“staticField=“+this.staticField”);
}
};
}
var BaseClass 1=NewBaseClass(“动态原型1”);
var BaseClass2=NewBaseClass(“动态原型2”);
new(NewDerivedClass(BaseClass1))(3,33.dump();
new(NewDerivedClass(BaseClass1))(4,44.dump();
new(NewDerivedClass(BaseClass2))(5,55.dump();
new(NewDerivedClass(BaseClass2))(6,66.dump();
//输出:
//instanceField=3 derivedField=33 staticField=动态原型#1
//instanceField=4 derivedField=44 staticField=动态原型#1
//instanceField=5 derivedField=55 staticField=动态原型#2
//instanceField=6 derivedField=66 staticField=动态原型#2

这种方法的一个危险是,您不能接受并修改作为NewDerivedClass一部分的泛型,这会阻止您修改任意类,部分原因是您可能会引入名称冲突。看

使用ES2015
static
请注意,上面没有使用Typescript的
static
,因为您已经将static字段有效地放在NewBaseClass的匿名原型对象上,而不是放在构造函数本身上。通过在派生函数中引用基类对象,而不是将其视为原始属性,可以很容易地解决这一问题

函数NewBaseClass(sF:string){
返回舱{

static staticField=sF;//“如果一大组对象都需要相同的val
interface DynamicClass_ { // not needed in TypeScript 3.8
  new (iF: number, dF: number): {
    instanceField: number;
    derivedField: number;
  };
}
function NewClass(staticField: string, foo: any): DynamicClass_ {
  class DynamicClass {
    constructor(public instanceField: number, 
                public derivedField: number) { }
    dump() {
      console.log("instanceField=" + this.instanceField +
          " derivedField=" + this.derivedField + 
          " staticField=" + staticField + // <<<<<<<<<<<<<<<<<<<<<<<<<
          " foo=" + foo);                 // <<<<<<<<<<<<<<<<<<<<<<<<<
    }
  }
  return DynamicClass;
}
function NewClass(staticField: string): DynamicClass_ {
  class DynamicClass {
    ...
  }

  let proto: any = DynamicClass.prototype;
  proto.staticField = staticField;

  return DynamicClass;
}