【问题标题】:How to extend a class without having to use super in ES6?如何在 ES6 中无需使用 super 来扩展类?
【发布时间】:2015-09-13 01:33:05
【问题描述】:

是否可以在不调用super方法调用父类的情况下扩展ES6中的类?

编辑:这个问题可能具有误导性。这是我们必须调用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 运行它

【问题讨论】:

  • 范围问题是什么意思?您是否遇到异常(以及在哪里)?
  • 在不调用 super() 的情况下调用派生类时,我得到了预期。我编辑了我的问题以使其更清楚:)
  • 你在什么环境下运行这个?
  • 如果你扩展另一个类,构造函数必须首先调用 super(),你别无选择。
  • @JonathandeM。谢谢,所以这就是我猜未来应该是这样的方式吗?

标签: javascript class inheritance ecmascript-6


【解决方案1】:

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

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

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

8.1.1.3.4 部分定义了决定this 在函数中的内容的逻辑。类的重要部分是this 可能处于"uninitialized" 状态,当处于这种状态时,尝试使用this 将引发异常。

9.2.2[[Construct]] 部分定义了通过newsuper 调用的函数的行为。调用基类构造函数时,this[[Construct]] 的第 8 步初始化,但对于所有其他情况,this 未初始化。构造结束时调用了GetThisBinding,所以如果super还没有被调用(从而初始化this),或者没有返回显式替换对象,构造函数调用的最后一行将抛出异常.

【讨论】:

  • 您能建议一种无需调用super()即可从类继承的方法吗?
  • 你认为在 ES6 中是否可以在不调用构造函数中的 super() 的情况下从类继承?
  • 感谢编辑 - 现在就在那儿;您可以使用return Object.create(new.target.prototype, …) 来避免调用超级构造函数。
  • 你应该添加如果构造函数被省略会发生什么:将根据MDN使用默认构造函数。
【解决方案2】:

新的 es6 类语法只是带有原型的“旧”es5“类”的另一种表示法。因此,您不能在不设置其原型(基类)的情况下实例化特定类。

这就像把奶酪放在三明治上而不做。另外你不能在制作三明治之前放奶酪,所以...

...在使用super() 调用超类之前使用this 关键字也是不允许的。

// 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.

Source

【讨论】:

  • 所以如果我理解正确的话,当不调用 super() 时,我将无法在我的英雄类上使用类 Character 中的任何方法?但这似乎并不完全正确,因为我可以从基类调用方法。所以我想我在调用构造函数时只需要 super
  • 1.在 OP 中,this 根本没有使用。 2. JS 不是三明治,在 ES5 中你总是可以使用this,甚至在调用你喜欢的任何其他函数之前(可能会或可能不会定义补充属性)
  • @amit 1. 现在我也不能使用this 了?! 2. 我的 JS 类代表一个三明治,在 ES6 中你不能总是使用this。我只是试图解释 es6 类(用一个比喻),没有人需要这种破坏性/不必要的 cmets。
  • @marcel 我对玩世不恭表示歉意,但是: 1. 是关于引入 OP 中不存在的新问题。 2 是指出您的要求是错误的(仍然是这种情况)
  • @marcel - 看我的回答
【解决方案3】:

有多个答案和 cmets 声明 super 必须constructor 中的第一行。那是完全错误的。 @loganfsmyth 答案具有要求的必要参考,但归结为:

继承 (extends) 构造函数必须在使用 this 之前和返回之前调用 super,即使不使用 this 也是如此

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

'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());

【讨论】:

  • "See fragment below (works in Chrome...)"打开chrome开发者控制台,点击“运行代码sn-p”:Uncaught ReferenceError: this is not defined。当然,你可以在super()之前使用构造函数中的方法,但是你不能使用之前的类中的方法!
  • 你不能在super()之前使用this(你的代码证明了这一点)与规范无关,而是与javascript的实现有关。所以,你必须在 'this' 之前调用 'super'。
  • @marcel - 我认为我们有很多困惑。我只是说(一直)在使用super 之前拥有STATEMENTS 是合法的,而您说在调用super 之前使用this 是非法的。我们都是对的,只是彼此不理解 :-) (而且那个例外是故意的,以表明什么是不合法的——我什至将属性命名为“WillFail”)
  • 我会说这个例子又快又脏。很抱歉直言不讳。它表明super 不必是第一个语句。但我为什么要这样?在调用基本构造函数之前生成一个 id?为什么不将它用于所有课程? derivedPropnull。它至少提出了很多问题。你让它听起来像你有一个实际应用的例子。但这个例子仍然是理论上的。
  • @x-yuri 这是一个已有 5 年历史的答案 :-) 但是,我仍然认为它的价值在于解释什么是有效或无效代码,而不是这样的用例代码。了解更多总是一件好事,即使您在任何时候都看不到用例。
【解决方案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

阅读this了解更多信息。

【讨论】:

    【解决方案5】:

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

    因为您本身并没有在子类中创建构造函数,而是仅引用在相应子类中被覆盖的方法。

    这意味着您将自己从强加给您的构造函数功能中解脱出来,并避免使用常规方法 - 如果您让自己选择,则可以覆盖并且不会强制执行 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");

    干杯!

    【讨论】:

    • 我需要一个“解释一下,就像我五岁一样”。我觉得这是一个非常深刻的答案,但很复杂,因此被忽略了
    • @swyx :魔术在构造函数内部,其中“this”指的是不同类型的对象,具体取决于您正在创建的对象类型。例如。如果您正在构建一个新的 DomainObservable,this.ObjectConstructor 指的是不同的方法,即 DomainObserveable.ObjectConstructor ;而如果你正在构建一个新的 ArrayObservable,this.ObjectConstructor 指的是 ArrayObservable.ObjectConstructor。
    • 看我的回答,我贴了一个更简单的例子
    • 我完全同意@swyx;这个答案做得太多了……我只是略读了一下,我已经累了。我感觉就像“解释说我五岁了,真的要小便......”
    【解决方案6】:

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

    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();
        }
    }
    

    不要扩展真正的constructor(),只需使用假的_constructor() 进行实例化逻辑。

    请注意,此解决方案使调试变得烦人,因为您必须为每个实例化单步执行一个额外的方法。

    【讨论】:

    • 是的,这是迄今为止最简单的方法和最明确的答案——我要问的问题是......“为什么,上帝,为什么!?”......有一个非常正当的理由super() 不是自动的...您可能希望将特定参数传递给基类,因此您可以在实例化基类之前进行一些处理/逻辑/思考。
    【解决方案7】:

    试试:

    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() 调用
    • 我认为你的目标不是执行父构造函数,这就是这段代码的作用。扩展时无法摆脱 super。
    • 抱歉误导了 :) 不,我只是想知道是否真的有必要使用 super(),因为我想知道语法,因为在其他语言中,我们不必调用 super 方法调用派生类的构造函数
    • 根据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发布
    • "所以我会说等待 ES6 发布" --- 它已经发布了,ecma-international.org/publications/files/ECMA-ST/Ecma-262.pdf
    【解决方案8】:

    如果您打算开发以下 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);
    });
    

    【讨论】:

      【解决方案9】:

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

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

      【讨论】:

      • 我不确定为什么上面所有的样板代码,我也不确定这种方法是否有副作用。它对我有用,没有问题。
      • 一个问题:如果你想再次扩展你的子类,你必须在每个子类的构造函数中构建 skipConstructor 功能
      【解决方案10】:

      @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 类型的对象(它的父类)并且在子BchildMethod() 内我们有this 指向我们在B 的constructorObject.create(new.target.prototype) 中创建的对象obj

      而这一切完全不关心super

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

      希望这对某人有所帮助。

      【讨论】:

        猜你喜欢
        • 2016-08-20
        • 2019-09-16
        • 1970-01-01
        • 1970-01-01
        • 2018-11-28
        • 1970-01-01
        • 1970-01-01
        • 2019-06-07
        相关资源
        最近更新 更多