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

Javascript Typescript:无法访问继承的类构造函数中的成员值,javascript,typescript,oop,inheritance,ecmascript-6,Javascript,Typescript,Oop,Inheritance,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); } } c

我从中继承了一个类
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();
运行此代码时,出现以下错误:

Uncaught TypeError: Cannot read property 'value' of undefined
如何避免这个错误

我很清楚,JavaScript代码在创建
myMember
之前会调用
init
方法,但是应该有一些实践/模式来让它工作。

尝试以下方法:

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

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

const x = new B();

超级必须是第一个命令。请记住,typescript更像是“带有类型文档的javascript”,而不是语言本身

如果您查看transpiled code.js,它是清晰可见的:

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

这就是为什么在某些语言(cough C#)中,代码分析工具会标记构造函数中虚拟成员的使用情况

In-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;
}

您应该考虑一个解决方案,其中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();   
或者,您可以为构造函数添加一个额外的参数,指定是否调用
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();
或者是非常脏的解决方案
setTimeout
,它将延迟初始化,直到当前帧完成。这将允许父构造函数调用完成,但在构造函数调用和超时到期之间会有一个过渡,即对象尚未初始化时

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 
像这样:

 class A
{
     myMember; 
    constructor() {

    }

    show() {
        alert(this.myMember.value);
    }
}

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

    constructor() {
        super();
    }
}

const test = new B;
test.show();

您可以采取的一种方法是为myMember使用getter/setter,并管理getter中的默认值。这将防止未定义的问题,并允许您保持几乎完全相同的结构。像这样:

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

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

    get myMember() {
        return this._myMember || { value: 1 };
    }

    set myMember(val) {
        this._myMember = val;
    }
}

const x = new B();

你必须在A类中调用init吗

这很好,但我不知道您是否有不同的要求:

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

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

const x = new B();

因为
myMember
属性是在父构造函数中访问的(
init()
是在
super()
调用期间调用的),所以无法在子构造函数中定义它,而不满足竞争条件

有几种替代方法

init
hook
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,例如基于承诺的:

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类在
super
之前对
this
的使用有限制,因此可以将子类分解为函数以规避此限制:

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);
}
接口B扩展了{}
接口BPrivate扩展了B{
myMember:{value:number};
}
接口BStatic扩展了{
新的():B;
}
常数B=函数B(此:B私有){
this.myMember={value:1};
回电话(本);
}
B.prototype.init=函数(){
log(this.myMember.value);
}

这很少是一个好的选择,因为desugared类应该在TypeScript中额外键入。这也不适用于本机父类(TypeScript
es6
esnext
target)。

是的,但在这种情况下,我必须重新声明
myMember
,我不想这样做。它也会这样做
init()
将在声明此.myMember之前被调用。@Adam-再次阅读答案,我已经发布了由typescript创建的.js文件。好的,没错,抱歉。但这并不是解决我问题的办法:-/@Adam-没有办法,你不能这样使用它