ES6でスーパーを使用せずにクラスを拡張する方法は?


98

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

私はこれをiojs --harmony with v2.3.0で実行しています


スコープの問題とはどういう意味ですか?例外(および場所)が発生していますか?
アミット2015年

super()を呼び出さずにクラスを呼び出すと、派生クラスに期待が寄せられます。私は質問をより明確にするために編集しました:)
xhallix '26

これを実行している環境は何ですか?
アミット2015年

6
別のクラスを拡張する場合、コンストラクターが最初にsuper()を呼び出す必要があるため、選択肢はありません。
ジョナサン・デ・M

@JonathandeM。ありがとう、これはそれが私が推測する将来のあるべき姿だと思いますか?
xhallix

回答:


147

ES2015(ES6)クラスのルールは基本的に次のようになります。

  1. 子クラスのコンストラクタでthisは、superが呼び出されるまで使用できません。
  2. ES6クラスコンストラクターsuperは、サブクラスの場合は呼び出す必要があります。そうでない場合は、初期化されなかったオブジェクトの代わりにオブジェクトを明示的に返す必要があります。

これは、ES2015仕様の2つの重要なセクションに分類されます。

セクション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、2015年

4
ES6 super()では、コンストラクターを呼び出さずにクラスから継承できると思いますか?
Bergi

8
編集をありがとう-それは今そこにあります。return Object.create(new.target.prototype, …)スーパーコンストラクターを呼び出さないようにすることができます。
Bergi、


1
コンストラクタが省略された場合の処理​​を追加する必要があります。デフォルトのコンストラクタはMDNに従って使用されます。
kleinfreund

11

内部の最初の行でsuper なければならないことを示す複数の回答とコメントがありましたconstructor。それは単に間違っています。@loganfsmyth回答には、必要な要件のリファレンスがありますが、要約すると次のようになります。

継承(extends)コンストラクタは、使用されていない場合でも、使用する前と戻る前に呼び出す必要がありますsuperthisthis

this呼び出す前にステートメントを(を使用せずに)持つことが理にかなっている理由については、以下のフラグメント(Chromeで動作します...)を参照してください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()メソッドを使用できますが、以前はクラスのメソッドを使用できません!
マルセル2015年

this以前に使用できないsuper()(コードがそれを証明する)ことは、仕様とは直接関係ありませんが、JavaScriptの実装とは関係ありません。したがって、「this」の前に「super」を呼び出す必要が
マルセル2015年

@marcel-多くの混乱があったと思います。私はそれを持って合法であることを(ずっと)述べた文を使用する前にsuper、あなたはそれを使用するのは違法であることを示すたthis呼び出す前にsuper。私たちはどちらも正しいですが、お互いを理解できませんでした:-)(その例外は意図的なもので、違法なものを示しています-プロパティに「WillFail」という名前を付けました)
Amit

9

新しいes6クラス構文は、プロトタイプを持つ「古い」es5「クラス」のもう1つの表記法にすぎません。したがって、プロトタイプ(基本クラス)を設定せずに特定のクラスをインスタンス化することはできません。

それはそれを作らずにあなたのサンドイッチにチーズを置くようなものです。また、サンドイッチを作るにチーズを入れることはできないので...

...で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クラスのメソッドを使用できなくなりますか?しかし、基本クラスからメソッドを呼び出すことができるため、これは完全には正しくないようです。だから私はコンストラクタを呼び出すときにスーパーが必要だと思います
xhallix '26

1. OPでは、thisまったく使用されません。2. JSはサンドイッチではありません。ES5 thisでは、他の好きな関数を呼び出す前でも、それをいつでも使用できます(補足プロパティを定義する場合としない場合があります)
Amit

@amit 1.そして今、私も使用できませthisんか?!2.私のJSクラスはサンドイッチを表しており、ES6では常に使用できるわけではありませんthis。私はes6クラスを(比喩で)説明しようとしているだけで、そのような破壊的/不要なコメントは誰にも必要ありません。
マルセル2015年

@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
私はこれについて「私は5人のように説明する」が必要です。これは非常に深い答えですが、複雑で無視されているようです
swyx

@swyx:マジックはコンストラクターの内部にあります。「this」は、作成するオブジェクトのタイプに応じて異なるタイプのオブジェクトを指します。たとえば、新しいDomainObservableを作成している場合、this.ObjectConstructorは別のメソッド、つまりDomainObserveable.ObjectConstructorを参照します。一方、新しいArrayObservableを作成する場合、this.ObjectConstructorはArrayObservable.ObjectConstructorを参照します。
cobberboy 2017

私の答えを見て、私ははるかに単純な例を投稿しました
マイケル・ルイス

@swyxに完全に同意します。この答えはあまりにも多くをやっています...私はそれをすくい取りました、そして私はすでに疲れています。私は「5
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による答えが最も簡単な方法ですが、彼の例は少し肥大化しています。これが一般的なバージョンです:

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()が自動ではない非常に正当な理由があります。 ..基本クラスに特定のパラメーターを渡したい場合があるので、基本クラスをインスタンス化する前に、いくつかの処理/論理/思考を行うことができます。
LFLFM 2019年

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

あなたの目標は親コンストラクタを実行することではないと思いました、それがこのコードがすることです。拡張時にスーパーを取り除くことはできません。
ジョナサン・デ・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のリリースを待つでしょう
Jonathan de M.


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 2017年

1
1つの問題:サブクラスをもう一度拡張する場合は、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さんに作成constructorしてObject.create(new.target.prototype)

そして、これをまったく気にせずにsuper

これは、JSではconstructor、クライアントコードがを使用して新しいインスタンスを作成するときに、完全に異なるオブジェクトを返すことができるという事実を利用していますnew

これが誰かを助けることを願っています。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.