【问题标题】:ES6 calling super() doesn't properly initialize parent classES6 调用 super() 没有正确初始化父类
【发布时间】:2016-09-16 09:14:02
【问题描述】:

我有以下代码结构,我尝试通过调用 super() 来初始化父类,但是当我调用 this._init() 时,它调用了子类。任何帮助我该如何解决这个问题?

class Parent {
    constructor() {
    console.log('P constructor()');
    this._init();
  }

  _init() {
    console.log('P _init()');
    this.parentProp = 'parent';
  }
}

class Child extends Parent {
    constructor() {
    console.log('C constructor');
    super();
    this._init();
  }

  _init() {
      console.log('C _init()');
    this.childProp = 'child';
  }

  test() {
    console.log(this.childProp + ' ' + this.parentProp);
  }
}

let child = new Child();
child.test();

这是上面代码的输出:

C constructor()
P constructor()
C _init()
C _init()
child undefined

【问题讨论】:

  • 你不能。您覆盖了方法,因此初始方法不再可用。
  • 我除了调用 super._init() 之外别无他法,我认为这不是一个好习惯,因为它属于父类。
  • “我认为这不是一个好的做法”——您的考虑与常见做法不一致。从子类调用基类方法并没有错。
  • “调用 super._init() 我认为这不是一个好习惯” 也不是调用 this._init()

标签: javascript inheritance ecmascript-6 babeljs


【解决方案1】:

Child#_init 被调用是因为当调用this._init()(在Parent 中)时,对象上的_init 属性所指的就是这个。会发生什么(省略一些细节)是:

  1. new 创建一个新对象,其[[Prototype]]Child.prototypeChild.prototype[[Prototype]]Parent.prototype
  2. new 致电Child
  3. Child 致电Parent
  4. this._init() 查找对象的 _init 属性。由于该对象没有自己的_init 属性,JavaScript 引擎会查找其[[Prototype]]Child.prototype 确实有一个 _init 属性,所以引擎使用那个。

至于解决它:JavaScript 类只有一个构造函数,因此拥有一个单独的_init 函数并没有真正的目的。1这就是构造函数的用途。尽管他们的名字,他们不构造对象,他们初始化他们。所以只要把_init的代码放在构造函数本身:

class Parent {
  constructor() {
    console.log('P constructor');
    this.parentProp = 'parent';
  }
}

class Child extends Parent {
  constructor() {
    console.log('C constructor');
    super();
    this.childProp = 'child';
  }

  test() {
    console.log(this.childProp + ' ' + this.parentProp);
  }
}

let child = new Child();
child.test();

或者,只需从Child 中完全删除this._init() 调用,然后让Child#_init 调用super._init()。我知道你在评论中说过你认为这是不好的做法(它不是,这是标准做法),但如果你想打破 _init 来分隔功能,那就是你要做的。但是这样做违反了主要的、成熟的跨语言,即从构造函数调用可覆盖的方法(Parent 调用this._init())是一个坏主意™。 :-)

如果您绝对坚持将代码分离到一个函数中,并且不想在Child#_init 中使用super._init(),则需要将其与类分开:

let Parent = (function() {
  class Parent {
    constructor() {
      console.log('P constructor');
      initParent.call(this);
    }
  }

  function initParent() {
    console.log("initParent");
    this.parentProp = 'parent';
  }

  return Parent;
})();

let Child = (function() {
  class Child extends Parent {
    constructor() {
      console.log('C constructor');
      super();
      initChild.call(this);
    }

    test() {
      console.log(this.childProp + ' ' + this.parentProp);
    }
  }

  function initChild() {
    console.log("initChild");
    this.childProp = 'child';
  }

  return Child;

})();

let child = new Child();
child.test();

1我可以看到在具有重载构造函数的语言中使用方法——尽管我主张让它们相互调用而不是实用方法——但不是在 JavaScript 中。

【讨论】:

  • “尽管他们的名字,他们不构造对象” --- 如果不初始化对象,“构造函数”应该做什么?
  • @zerkms 只有newsuper() 真正创建对象,.constructor 方法的工作是初始化该实例。
  • @zerkms:我不确定我是否理解你的问题。答案中的引述是“尽管他们的名字,他们不构造对象,他们初始化他们。”.
  • @T.J.Crowder 我只是想刻薄,“构造函数”和“初始化程序”是无法区分的过程,甚至可能没有单一的“官方”定义:-)
  • 感谢@T.J.Crowder Zerkms 和 Bergi,这真的很有用!
【解决方案2】:

最好的解决方案是根本不使用init 方法。构造函数不应调用可覆盖的方法:

class Parent {
  constructor() {
    console.log('P constructor()');
    this.parentProp = 'parent';
  }
}

class Child extends Parent {
  constructor() {
    console.log('C constructor');
    super();
    this.childProp = 'child';
  }
  test() {
    console.log(this.childProp + ' ' + this.parentProp);
  }
}

let child = new Child();
child.test();

或者,在您的情况下,它可以在 _init 中调用 super 方法,而不是从 Child 构造函数调用 _init

class Child extends Parent {
  constructor() {
    console.log('C constructor');
    super();
  }
  _init() {
    super._init();
    console.log('C _init()');
    this.childProp = 'child';
  }
  …

【讨论】:

  • @T.J.Crowder 实际上 Parent constructor 应该能够毫无问题地调用它,因此如果他将 Child 更改为我建议的内容,则 OP 代码将起作用。当然,这是一个不好的反向控制解决方案。在构造函数完成之前调用可覆盖的方法通常是在自找麻烦(这里只是无关紧要,因为 Child 构造函数不做任何事情)
  • 是的,确实如此,我想root可以做到。尽管正如您所说,从构造函数调用可覆盖方法是一种众所周知的反模式。
【解决方案3】:

在我们的例子中,super() 基本上等于Parent.call(this),其中this 将指向Child。所以Child.prototype._init 将被调用两次,并且不会创建该属性parentProp

这就是您看到以下控制台输出的原因。

C constructor()
P constructor()
C _init() //printed from Child.prototype._init
C _init() //printed from Child.prototype._init
child undefined

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-06-30
    • 2017-03-08
    • 2016-02-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多