如何在ES6中不必使用super来扩展类?


98

是否可以在ES6中扩展类而无需调用super方法来调用父类?

编辑:这个问题可能会引起误解。这是我们必须打电话的标准,super()还是我遗漏了一些东西?

例如:

class Character {
   constructor(){
      console.log('invoke character');
   }
}

class Hero extends Character{
  constructor(){
      super(); // exception thrown here when not called
      console.log('invoke hero');
  }
}

var hero = new Hero();

当我不调用super()派生类时,我遇到了范围问题->this is not defined

我正在v2.3.0中使用iojs --harmony运行它


你是什​​么意思范围问题?您是否有例外(在哪里)?
阿米特(Amit)2015年

我在不调用super()的情况下调用派生类时就得到了期望。我编辑了我的问题以使其更清楚:)
xhallix

您在什么环境中运行它?
阿米特(Amit)2015年

6
如果您扩展另一个类,构造函数必须首先调用super(),则别无选择。
Jonathan de M.

@乔纳森德 谢谢,所以我猜这就是将来应该的样子吗?
xhallix

Answers:


147

ES2015(ES6)类的规则基本上可以归结为:

  1. 在子类构造函数中,this只有在super被调用之前才能使用。
  2. ES6类构造函数必须super是子类,否则必须调用,否则它们必须显式返回某个对象以代替未初始化的对象。

这归结为ES2015规范的两个重要部分。

8.1.1.3.4节定义了决定this功能的逻辑。类的重要部分是有可能this处于一种"uninitialized"状态,当处于这种状态时,尝试使用this将引发异常。

9.2.2节,[[Construct]]定义了通过new或调用的函数的行为super。调用基类构造函数时,this在的步骤#8进行了初始化[[Construct]],但在所有其他情况下,this都未初始化。在构造结束时,GetThisBinding会调用,因此,如果super尚未调用(因此初始化this),或者未返回显式替换对象,则构造函数调用的最后一行将引发异常。


1
您能否建议一种无需调用即可从类继承的方法super()
Bergi

4
您认为在ES6中有可能从类中继承而不调用super()构造函数吗?
Bergi

8
感谢您的修改-现在就在这里;您return Object.create(new.target.prototype, …)可以避免调用超级构造函数。
Bergi

2

1
您应该添加如果省略构造函数会发生的情况:根据MDN,将使用默认构造函数。
kleinfreund

11

已经有多个答案和评论,指出super 必须是其中的第一行constructor。那是完全错误的。@loganfsmyth答案具有要求的必需参考,但可以归结为:

即使未使用Inheriting(extends)构造函数,也必须super在使用this之前和返回之前调用this

请参阅下面的片段(可在Chrome中使用...),以了解为什么this在调用之前使用语句(不使用)可能有意义super

'use strict';
var id = 1;
function idgen() {
  return 'ID:' + id++;
}

class Base {
  constructor(id) {
    this.id = id;
  }

  toString() { return JSON.stringify(this); }
}

class Derived1 extends Base {
  constructor() {
    var anID = idgen() + ':Derived1';
    super(anID);
    this.derivedProp = this.baseProp * 2;
  }
}

alert(new Derived1());


2
"See fragment below (works in Chrome...)"打开chrome开发者控制台,然后点击“运行代码段”:Uncaught ReferenceError: this is not defined。当然,您可以在构造函数中使用方法,super()但是之前不能在类中使用方法!
marcel 2015年

this之前无法使用super()的代码(您的代码证明了这一点)与规范无关,而是与javascript的实现无关。因此,您必须在“ this”之前调用“ super”。
marcel 2015年

@marcel-我觉得我们很困惑。我只是说(一直),在使用STATEMENTS之前是合法的super,而您说的是this在致电之前使用STATEMENTS是非法的super。我们都是对的,只是彼此不了解:-)(而且该异常是有意的,以显示不合法的内容-我什至将该属性命名为“ WillFail”)
Amit

9

新的es6类语法只是带有原型的“旧” es5“类”的另一种表示法。因此,如果不设置特定类的原型(基类),则无法实例化该特定类。

那就像在不做的情况下将奶酪放在三明治上。同样,做三明治之前,您不能放奶酪,所以...

... 也不允许this在调用父类之前使用关键字super()

// valid: Add cheese after making the sandwich
class CheeseSandwich extend Sandwich {
    constructor() {
        super();
        this.supplement = "Cheese";
    }
}

// invalid: Add cheese before making sandwich
class CheeseSandwich extend Sandwich {
    constructor() {
        this.supplement = "Cheese";
        super();
    }
}

// invalid: Add cheese without making sandwich
class CheeseSandwich extend Sandwich {
    constructor() {
        this.supplement = "Cheese";
    }
}

如果未为基类指定构造函数,则使用以下定义:

constructor() {}

对于派生类,使用以下默认构造函数:

constructor(...args) {
    super(...args);
}

编辑:在上找到此developer.mozilla.org

When used in a constructor, the super keyword appears alone and must be used before the this keyword can be used.

资源


因此,如果我对您的理解正确,当不调用super()时,将无法使用Hero类上的Character类中的任何方法?但这似乎并不完全正确,因为我可以从基类中调用方法。所以我想我在调用构造函数时只需要super即可
xhallix

1.在OP中,this根本不使用。2. JS不是三明治,在ES5中this,即使在调用任何您喜欢的其他函数之前(可能会定义补充属性,也可能未定义),您都可以始终使用,
Amit 2015年

@amit 1.现在我也不能使用this?2.我的JS类代表一个三明治,在ES6中不能总是使用this。我只是想用一个隐喻来解释es6类,没有人需要这样的破坏性/不必要的评论。
marcel

@marcel我对犬儒主义表示歉意,但是:1.关于引入OP中不存在的新问题。2是要提醒您注意您的主张是错误的(仍然是这种情况)
Amit

@marcel-看到我的答案
阿米特(Amit)

4

刚刚注册发布此解决方案,因为这里的答案至少令我满意,因为实际上有一种简单的解决方法。调整类创建模式,以仅使用超级构造函数覆盖子方法中的逻辑,并将构造函数参数转发给它。

就像您在您自己的子类中自己创建一个构造函数一样,只引用在各自的子类中被覆盖的方法。

这意味着您将自己从强制执行的构造函数功能中解放出来,并避免使用常规方法 -在让您自行选择是否,何地以及如何调用super(完全)时,可以重写该方法并且不强制执行super()。可选),例如:

super.ObjectConstructor(...)

class Observable {
  constructor() {
    return this.ObjectConstructor(arguments);
  }

  ObjectConstructor(defaultValue, options) {
    this.obj = { type: "Observable" };
    console.log("Observable ObjectConstructor called with arguments: ", arguments);
    console.log("obj is:", this.obj);
    return this.obj;
  }
}

class ArrayObservable extends Observable {
  ObjectConstructor(defaultValue, options, someMoreOptions) {
    this.obj = { type: "ArrayObservable" };
    console.log("ArrayObservable ObjectConstructor called with arguments: ", arguments);
    console.log("obj is:", this.obj);
    return this.obj;
  }
}

class DomainObservable extends ArrayObservable {
  ObjectConstructor(defaultValue, domainName, options, dependent1, dependent2) {
    this.obj = super.ObjectConstructor(defaultValue, options);
    console.log("DomainObservable ObjectConstructor called with arguments: ", arguments);
    console.log("obj is:", this.obj);
    return this.obj;
  }
}

var myBasicObservable = new Observable("Basic Value", "Basic Options");
var myArrayObservable = new ArrayObservable("Array Value", "Array Options", "Some More Array Options");
var myDomainObservable = new DomainObservable("Domain Value", "Domain Name", "Domain Options", "Dependency A", "Depenency B");

干杯!


2
我需要一个“像我五岁的
孩子

@swyx:魔术位于构造函数内部,其中“ this”是指不同类型的对象,具体取决于您要创建的对象类型。例如,如果您要构造一个新的DomainObservable,则this.ObjectConstructor引用另一个方法,即DomainObservable.ObjectConstructor; 如果您要构造一个新的ArrayObservable,则this.ObjectConstructor引用ArrayObservable.ObjectConstructor。
cobberboy

看看我的回答,我发布了一个简单得多的示例
Michael Lewis,

我完全同意@swyx; 这个答案做得太多...我只是略读而已,我已经很累了。我感觉就像“像我五岁一样,真要尿尿……”
spb

4

如果在子类中完全省略了构造函数,则可以在子类中省略super()。“隐藏”默认构造函数将自动包含在您的子类中。但是,如果确实在子类中包含构造函数,则必须在该构造函数中调用super()。

class A{
   constructor(){
      this.name = 'hello';   
   }
}
class B extends A{
   constructor(){
      // console.log(this.name); // ReferenceError
      super();
      console.log(this.name);
   }
}
class C extends B{}  // see? no super(). no constructor()

var x = new B; // hello
var y = new C; // hello

阅读以获得更多信息。


2

justyourimage的答案是最简单的方法,但是他的例子有点is肿。这是通用版本:

class Base {
    constructor(){
        return this._constructor(...arguments);
    }

    _constructor(){
        // just use this as the constructor, no super() restrictions
    }
}

class Ext extends Base {
    _constructor(){ // _constructor is automatically called, like the real constructor
        this.is = "easy"; // no need to call super();
    }
}

不要扩展real constructor(),只需将false _constructor()用于实例化逻辑即可。

注意,此解决方案使调试变得烦人,因为您必须为每个实例都采用额外的方法。


是的,这是迄今为止最简单的方法和最清晰的答案-我问的问题是……“为什么,上帝,为什么!?”……super()不是自动的,这是非常有道理的原因。 ..您可能希望将特定的参数传递给您的基类,因此您可以在实例化基类之前进行一些处理/逻辑/思考。
LFLFM

1

尝试:

class Character {
   constructor(){
     if(Object.getPrototypeOf(this) === Character.prototype){
       console.log('invoke character');
     }
   }
}


class Hero extends Character{
  constructor(){
      super(); // throws exception when not called
      console.log('invoke hero');
  }
}
var hero = new Hero();

console.log('now let\'s invoke Character');
var char = new Character();

Demo


在此示例中,您还使用了super(),如果不使用它,则会引发异常。因此,我认为在这种情况下不可能忽略此super()调用
xhallix

我以为您的目标不是执行父构造函数,这就是这段代码的作用。扩展时不能摆脱super。
Jonathan de M.

抱歉,我被误导了:)不,我只是想知道是否真的需要使用super(),因为我想知道语法,因为在其他语言中,当我们为派生类调用构造函数时,我们不必调用super方法
xhallix

1
developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/...A constructor *can* use the super keyword to call the constructor of a parent class.,所以我会说等待ES6发布
乔纳森·德M.

3
“所以我要说等待ES6发布” ---它已经被发布了,ecma-international.org
publications / files /

1

如果您打算开发以下OOP概念,我建议使用OODK-JS

OODK(function($, _){

var Character  = $.class(function ($, µ, _){

   $.public(function __initialize(){
      $.log('invoke character');
   });
});

var Hero = $.extends(Character).class(function ($, µ, _){

  $.public(function __initialize(){
      $.super.__initialize();
      $.log('invoke hero');
  });
});

var hero = $.new(Hero);
});

0

简单的解决方案:我认为它显然不需要解释。

class ParentClass() {
    constructor(skipConstructor = false) { // default value is false
        if(skipConstructor) return;
        // code here only gets executed when 'super()' is called with false
    }
}
class SubClass extends ParentClass {
    constructor() {
        super(true) // true for skipping ParentClass's constructor.
        // code
    }
}

我不确定为什么要使用上述所有样板代码,也不能确定这种方法是否有副作用。它对我毫无问题。
MyUserInStackOverflow

1
一个问题:如果要再次扩展SubClass,则必须在每个SubClass的构造函数中构建skipConstructor功能
Michael Lewis

0

@Bergi提到过new.target.prototype,但是我一直在寻找一个具体的示例,以证明您无需调用就可以访问this(或者更好地,使用客户端代码创建的对象的引用new,请参见下文)super()

谈话很便宜,请向我展示代码...这是一个示例:

class A { // Parent
    constructor() {
        this.a = 123;
    }

    parentMethod() {
        console.log("parentMethod()");
    }
}

class B extends A { // Child
    constructor() {
        var obj = Object.create(new.target.prototype)
        // You can interact with obj, which is effectively your `this` here, before returning
        // it to the caller.
        return obj;
    }

    childMethod(obj) {
        console.log('childMethod()');
        console.log('this === obj ?', this === obj)
        console.log('obj instanceof A ?', obj instanceof A);
        console.log('obj instanceof B ?',  obj instanceof B);
    }
}

b = new B()
b.parentMethod()
b.childMethod(b)

将输出:

parentMethod()
childMethod()
this === obj ? true
obj instanceof A ? true
obj instanceof B ? true

因此,您可以看到我们正在有效地创建类型B(子类)的对象,这也是类型A(其父类)的对象,并且在childMethod()子类中,B我们this指向obj了在B的constructorwith中创建的对象Object.create(new.target.prototype)

而这一切都根本不在乎super

这利用了以下事实:constructor当客户端代码使用构造一个新实例时,在JS中a 可以返回完全不同的对象new

希望这对某人有帮助。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.