因为myMember
属性是在父构造函数中访问的(init()
在调用期间被super()
调用),所以无法在不遇到竞争条件的情况下在子构造函数中定义它。
有几种替代方法。
init
钩
init
被视为不应在类构造函数中调用的钩子。相反,它被显式调用:
new B();
B.init();
或者它被框架隐式调用,作为应用程序生命周期的一部分。
静态属性
如果一个属性应该是一个常量,它可以是静态属性。
这是最有效的方法,因为这是静态成员的用途,但语法可能没有那么吸引人,因为this.constructor
如果子类中应正确引用静态属性,则它需要使用而不是类名:
class B extends A {
static readonly myMember = { value: 1 };
init() {
console.log((this.constructor as typeof B).myMember.value);
}
}
属性 getter/setter
可以使用get
/set
语法在类原型上定义属性描述符。如果一个属性应该是原始常量,它可以只是一个 getter:
class B extends A {
get myMember() {
return 1;
}
init() {
console.log(this.myMember);
}
}
如果属性不是恒定的或原始的,它会变得更加棘手:
class B extends A {
private _myMember?: { value: number };
get myMember() {
if (!('_myMember' in this)) {
this._myMember = { value: 1 };
}
return this._myMember!;
}
set myMember(v) {
this._myMember = v;
}
init() {
console.log(this.myMember.value);
}
}
就地初始化
一个属性可以在它首先被访问的地方被初始化。如果这发生在可以在类构造函数之前访问的init
方法中,这应该发生在那里:this
B
class B extends A {
private myMember?: { value: number };
init() {
this.myMember = { value: 1 };
console.log(this.myMember.value);
}
}
异步初始化
init
方法可能会变得异步。初始化状态应该是可跟踪的,因此该类应该为此实现一些 API,例如基于 promise:
class A {
initialization = Promise.resolve();
constructor(){
this.init();
}
init(){}
}
class B extends A {
private myMember = {value:1};
init(){
this.initialization = this.initialization.then(() => {
console.log(this.myMember.value);
});
}
}
const x = new B();
x.initialization.then(() => {
// class is initialized
})
对于这种特殊情况,这种方法可能被视为反模式,因为初始化例程本质上是同步的,但它可能适用于异步初始化例程。
脱糖课
由于ES6类对使用限制this
之前super
,子类可以被脱到一个函数来规避这一限制:
interface B extends A {}
interface BPrivate extends B {
myMember: { value: number };
}
interface BStatic extends A {
new(): B;
}
const B = <BStatic><Function>function B(this: BPrivate) {
this.myMember = { value: 1 };
return A.call(this);
}
B.prototype.init = function () {
console.log(this.myMember.value);
}
这很少是一个好的选择,因为应该在 TypeScript 中额外输入脱糖类。这也不适用于本机父类(TypeScriptes6
和esnext
目标)。