Общее решение
Следующий код производит тот же эффект в TypeScript (staticField
находится в цепочке прототипов, а не в производном объекте). Однако обратите внимание, что использовать действительно static
поле в базовом классе проще: вам не нужно писать as BaseClass
в NewBaseClass
.
- TypeScript 3.8.3 не делает полностью примите это: он жалуется на
DerivedClass
, говоря: «Класс mixin должен иметь конструктор с единственным параметром rest типа 'any []'". Однако эту ошибку можно устранить с помощью // @ts-ignore
. В TypeScript 3.6.5, похоже, не понимается, что baseClass
не пусто и поэтому выдает несколько ошибок. В нем также говорится «Возвращаемый тип экспортированной функции имеет или использует закрытое имя DerivedClass», что странно, поскольку NewDerivedClass
не экспортируется. Обходной путь для последней ошибки - определить соответствующий интерфейс и использовать его в качестве возвращаемого типа:
interface DerivedClass_ {
new (iF: number, dF: number): {
derivedField: number;
dump(): void;
}
}
interface BaseClass {
new (iF: number): {
instanceField: number;
staticField: string;
};
}
function NewBaseClass(sF: string): BaseClass {
class DynamicBaseClass {
instanceField: number;
staticField?: string; // a value assigned here wouldn't be on the prototype
constructor(iF: number) { this.instanceField = iF; }
}
DynamicBaseClass.prototype.staticField = sF;
return DynamicBaseClass as BaseClass;
}
function NewDerivedClass<Base extends BaseClass>(baseClass: Base) {
// @ts-ignore "A mixin class must have a constructor with a single rest parameter..."
class DerivedClass extends baseClass {
derivedField: number;
constructor(iF: number, dF: number) {
super(iF);
this.derivedField = dF;
}
dump() {
console.log("instanceField=" + this.instanceField +
" derivedField=" + this.derivedField +
" staticField=" + this.staticField +
" base=" + (this as any).__proto__.__proto__.constructor.name);
}
}
return DerivedClass;
}
var BaseClass1 = NewBaseClass("dynamic prototype chain #1");
var BaseClass2 = NewBaseClass("dynamic prototype chain #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();
Вывод компилятора выглядит так в TS 3.6.3 и работает как ожидалось:
"use strict";
var __extends = (this && this.__extends) || (function () {
var extendStatics = function (d, b) {
extendStatics = Object.setPrototypeOf ||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
return extendStatics(d, b);
};
return function (d, b) {
extendStatics(d, b);
function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};
})();
function NewBaseClass(sF) {
var DynamicBaseClass = /** @class */ (function () {
function DynamicBaseClass(iF) {
this.instanceField = iF;
}
return DynamicBaseClass;
}());
DynamicBaseClass.prototype.staticField = sF;
return DynamicBaseClass;
}
function NewDerivedClass(baseClass) {
// @ts-ignore "A mixin class must have a constructor with a single rest parameter of type 'any[]'."
var DerivedClass = /** @class */ (function (_super) {
__extends(DerivedClass, _super);
function DerivedClass(iF, dF) {
var _this = _super.call(this, iF) || this;
_this.derivedField = dF;
return _this;
}
DerivedClass.prototype.dump = function () {
console.log("instanceField=" + this.instanceField +
" derivedField=" + this.derivedField +
" staticField=" + this.staticField +
" base=" + this.__proto__.__proto__.constructor.name);
};
return DerivedClass;
}(baseClass));
return DerivedClass;
}
var BaseClass1 = NewBaseClass("dynamic prototype chain #1");
var BaseClass2 = NewBaseClass("dynamic prototype chain #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();
Я вижу, он использует Object.setPrototypeOf
, который MDN предупреждает нас не использовать по соображениям производительности. Я надеюсь, что люди из TypeScript знают, что делают!
Техника для «дешевого» обмена данными
Если цель состоит в том, чтобы просто делиться данными между многими экземплярами, не занимая при этом память в отдельных экземплярах, это это можно сделать намного проще, как это:
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;
}
Обратите внимание, что dump()
может ссылаться на параметры, не сохраняя их в классе где-либо! В общем, среда выполнения JS должна создавать некоторый объект кучи для функций класса, таких как dump()
, для совместного использования. Логически, он не может сохранить параметры (staticField
et c.) В экземпляре (this
), потому что можно изменить this
с помощью кода, подобного new (NewClass(...))(...).dump.bind(otherThis)
- и все же отскок dump
по-прежнему будет иметь доступ к параметрам NewClass
.
Я почти уверен, что объекты, представляющие функции внутри DynamicClass
, должны создаваться заново каждый раз, когда вызывается NewClass
, потому что эти объекты доступны для JS программ. Таким образом, любой метод, который включает в себя возврат классов или функций из другой функции, повлечет за собой определенную стоимость памяти. В зависимости от обстоятельств эта стоимость может быть меньше или больше, чем хранение данных в экземплярах класса.
При использовании этого метода может быть полезно скопировать параметры в прототип для целей отладки:
function NewClass(staticField: string): DynamicClass_ {
class DynamicClass {
...
}
let proto: any = DynamicClass.prototype;
proto.staticField = staticField;
return DynamicClass;
}