Typescript:无法访问继承的类构造函数中的成员值

Ada*_*dam 19 javascript oop inheritance typescript ecmascript-6

我有一个类A,还有一个B继承自它的类.

class A {
    constructor(){
        this.init();
    }
    init(){}
}

class B extends A {
    private myMember = {value:1};
    constructor(){
        super();
    }
    init(){
        console.log(this.myMember.value);
    }
}

const x = new B();
Run Code Online (Sandbox Code Playgroud)

当我运行此代码时,我收到以下错误:

Uncaught TypeError: Cannot read property 'value' of undefined
Run Code Online (Sandbox Code Playgroud)

我怎样才能避免这个错误?

对我来说很明显,JavaScript代码会init在创建之前调用该方法myMember,但应该有一些实践/模式来使其工作.

Tit*_*mir 17

这就是为什么在某些语言(咳嗽C#)代码分析工具标记构造函数内虚拟成员的使用.

在Typescript字段中,在调用基础构造函数之后,在构造函数中进行初始化.字段初始化写在字段附近的事实只是语法糖.如果我们查看生成的代码,问题就会变得清晰:

function B() {
    var _this = _super.call(this) || this; // base call here, field has not been set, init will be called
    _this.myMember = { value: 1 }; // field init here
    return _this;
}
Run Code Online (Sandbox Code Playgroud)

您应该考虑一种解决方案,其中init从实例外部调用,而不是在构造函数中调用:

class A {
    constructor(){
    }
    init(){}
}

class B extends A {
    private myMember = {value:1};
    constructor(){
        super();
    }
    init(){
        console.log(this.myMember.value);
    }
}

const x = new B();
x.init();   
Run Code Online (Sandbox Code Playgroud)

或者,您可以为构造函数添加一个额外参数,以指定是否调用init,也不在派生类中调用它.

class A {
    constructor()
    constructor(doInit: boolean)
    constructor(doInit?: boolean){
        if(doInit || true)this.init();
    }
    init(){}
}

class B extends A {
    private myMember = {value:1};
    constructor()
    constructor(doInit: boolean)
    constructor(doInit?: boolean){
        super(false);
        if(doInit || true)this.init();
    }
    init(){
        console.log(this.myMember.value);
    }
}

const x = new B();
Run Code Online (Sandbox Code Playgroud)

或非常非常脏的解决方案setTimeout,它将推迟初始化直到当前帧完成.这将让父构造函数调用完成,但构造函数调用之间会有一个临时的时间,并且当对象未被init编辑时超时到期

class A {
    constructor(){
        setTimeout(()=> this.init(), 1);
    }
    init(){}
}

class B extends A {
    private myMember = {value:1};
    constructor(){
        super();
    }
    init(){
        console.log(this.myMember.value);
    }
}

const x = new B();
// x is not yet inited ! but will be soon 
Run Code Online (Sandbox Code Playgroud)


Est*_*ask 5

因为myMember在父构造函数中访问属性(init()在调用期间super()调用),所以在没有遇到竞争条件的情况下,如何在子构造函数中定义它.

有几种替代方法.

init

init被认为是一个不应该在类构造函数中调用的钩子.相反,它被明确调用:

new B();
B.init();
Run Code Online (Sandbox Code Playgroud)

或者它是由框架隐式调用的,作为应用程序生命周期的一部分.

静态属性

如果属性应该是常量,则它可以是静态属性.

这是最有效的方法,因为这是静态成员的用途,但语法可能不那么有吸引力,因为this.constructor如果在子类中正确引用静态属性,则需要使用而不是类名:

class B extends A {
    static readonly myMember = { value: 1 };

    init() {
        console.log((this.constructor as typeof B).myMember.value);
    }
}
Run Code Online (Sandbox Code Playgroud)

物业获取者/设定者

可以使用get/ setsyntax 在类原型上定义属性描述符.如果属性应该是原始常量,那么它可以只是一个getter:

class B extends A {
    get myMember() {
        return 1;
    }

    init() {
        console.log(this.myMember);
    }
}
Run Code Online (Sandbox Code Playgroud)

如果属性不是常数或原始的话,它变得更加hacky:

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);
    }
}
Run Code Online (Sandbox Code Playgroud)

就地初始化

可以在首先访问属性的位置初始化属性.如果在类构造函数之前可以访问的init方法中发生这种情况,那么应该在那里发生:thisB

class B extends A {
    private myMember?: { value: number };

    init() {
        this.myMember = { value: 1 }; 
        console.log(this.myMember.value);
    }
}
Run Code Online (Sandbox Code Playgroud)

异步初始化

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
})
Run Code Online (Sandbox Code Playgroud)

对于这种特殊情况,这种方法可以被认为是反模式,因为初始化例程本质上是同步的,但它可能适用于异步初始化例程.

Desugared类

由于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);
}
Run Code Online (Sandbox Code Playgroud)

这很少是一个很好的选择,因为Desugared类应该在TypeScript中另外输入.这也不适用于本机父类(TypeScript es6esnexttarget).