ES 6クラス継承とsuper


テキストhttps://javascript.info/class...
クラス継承とスーパー
クラスは他のクラスからextensできます.これはいい文法です.技術的には原型に基づいて継承します.
オブジェクトを継承するには、{..}の前にextendsと親オブジェクトを指定する必要があります.
このRabbitAnimalから継承されている.
class Animal {

  constructor(name) {
    this.speed = 0;
    this.name = name;
  }

  run(speed) {
    this.speed += speed;
    alert(`${this.name} runs with speed ${this.speed}.`);
  }

  stop() {
    this.speed = 0;
    alert(`${this.name} stopped.`);
  }

}


// Inherit from Animal
class Rabbit extends Animal {
  hide() {
    alert(`${this.name} hides!`);
  }
}


let rabbit = new Rabbit("White Rabbit");

rabbit.run(5); // White Rabbit runs with speed 5.
rabbit.hide(); // White Rabbit hides!
あなたが見ているように、extendのキーワードは、実際にRabbit.prototype[Prototype]]を追加し、Animal.prototypeに参照してください.
したがって、rabbitは、自身の方法にアクセスすることも、Animalの方法にアクセスすることもできる.extendsの後は、式に対応することができます.
Class文法の‘extens’の後に続くのは1つのクラスを指定するだけではなくて、更に表現式であることができます.
例えば、親クラスを生成する関数:
function f(phrase) {
  return class {
    sayHi() { alert(phrase) }
  }
}


class User extends f("Hello") {}


new User().sayHi(); // Hello
例では、class Userは、f('Hello')の戻りの結果を引き継いでいる.
高級プログラミングモードに対して,多くの条件に従って関数を用いて生成したクラスを使用すると,これは有用である.
書き換え方法
次のステップに進み、もう一つの方法を書き直しましょう.これまでRabbitAnimalからstopの方法を継承してきた.this.speed = 0で自分のRabbitを指定したら、優先的に使用されます.
class Rabbit extends Animal {
  stop() {
    // ...this will be used for rabbit.stop()
  }
}
……しかし、基本的には父の方法に代わるのではなく、父の方法に基づいて機能を調整したり拡張したりしたいです.前/後または途中で親の方法を呼び出させるように、いくつかの操作を行います.
Classはこのためにstopキーワードを提供する.
  • は、superを使用して親方法を起動する.
  • は、super.method(...)を使用して、父構造関数(constructor関数のみ)を呼び出します.
  • 例えば、ウサギはsuper(...)の時に自動的に隠れるようにします.
    class Animal {
    
      constructor(name) {
        this.speed = 0;
        this.name = name;
      }
    
      run(speed) {
        this.speed += speed;
        alert(`${this.name} runs with speed ${this.speed}.`);
      }
    
      stop() {
        this.speed = 0;
        alert(`${this.name} stopped.`);
      }
    
    }
    
    class Rabbit extends Animal {
      hide() {
        alert(`${this.name} hides!`);
      }
    
    
      stop() {
        super.stop(); // call parent stop
        this.hide(); // and then hide
      }
    
    }
    
    let rabbit = new Rabbit("White Rabbit");
    
    rabbit.run(5); // White Rabbit runs with speed 5.
    rabbit.stop(); // White Rabbit stopped. White rabbit hides!
    次に、stopRabbit方法は、stopを介して親タイプの方法を呼び出す.
    矢印関数はsuper.stop()ではありません.
    arrow-functionsの章で述べたように、矢印関数はsuperにありません.
    外部関数からsuperを取得する.たとえば:
    class Rabbit extends Animal {
      stop() {
        setTimeout(() => super.stop(), 1000); // call parent stop after 1sec
      }
    }
    矢印関数のsupersuperと同じであるので、期待どおりに動作する.ここで普通の関数を使うと、エラーが発生します.
    // Unexpected super
    setTimeout(function() { super.stop() }, 1000);
    構造関数を書き換える
    コンストラクタにとってはちょっと苦手なtrickyです.
    今まで、stop()は自分のRabbitがありません.Till now,constructor did not have its own Rabbit.
    仕様によれば、1つのクラスが他のクラスに拡張され、constructorがない場合、次のようなconstructorが自動的に生成される.
    class Rabbit extends Animal {
      // generated for extending classes without own constructors
    
      constructor(...args) {
        super(...args);
      }
    
    }
    constructorを呼び出してすべてのパラメータを伝えることができます.私たちが自分で構造関数を書かないと、このような状況が発生します.
    次にカスタムコンストラクタをconstructorに追加します.Rabbit以外に、nameを設置します.
    class Animal {
      constructor(name) {
        this.speed = 0;
        this.name = name;
      }
      // ...
    }
    
    class Rabbit extends Animal {
    
    
      constructor(name, earLength) {
        this.speed = 0;
        this.name = name;
        this.earLength = earLength;
      }
    
    
      // ...
    }
    
    
    // Doesn't work!
    let rabbit = new Rabbit("White Rabbit", 10); // Error: this is not defined.
    
    あら、間違えました今はウサギができません.なぜですか?
    簡単には、クラスを継承する構造関数は、earLengthを呼び出して(!)、super(...)を使用する前に実行しなければならない.
    どうしてですか?これはどういう状況ですか?えっと、この要求は確かにおかしいです.
    詳細を検討して、その原因を本当に理解させます.
    JavaScriptでは、他のクラスの構造関数を継承するのが特殊です.継承クラスでは、対応する構造関数を特殊な内部属性thisとしてマークしている.
    違いは:
  • 普通のコンストラクタが動作すると、thisとして空のオブジェクトを作成して実行します.
  • しかし、派生した構造関数が動作するときは、上記とは違って、父の構造関数がこの仕事を完成することを期待している.
  • したがって、私たちが自分たちの構造関数を構築している場合、[[ConstructorKind]]:“derived”を呼び出す必要があります.そうでないとsuperを持つオブジェクトは作成されなくなり、エラーが発生します.thisの場合、Rabbitを使用する前にthisを呼び出す必要があります.
    class Animal {
    
      constructor(name) {
        this.speed = 0;
        this.name = name;
      }
    
      // ...
    }
    
    class Rabbit extends Animal {
    
      constructor(name, earLength) {
    
        super(name);
    
        this.earLength = earLength;
      }
    
      // ...
    }
    
    
    // now fine
    let rabbit = new Rabbit("White Rabbit", 10);
    alert(rabbit.name); // White Rabbit
    alert(rabbit.earLength); // 10
    
    スーパーの実現と[HomeObject]super()の底辺実装をもっと深く理解してみましょう.私たちはいくつかの面白いことを見ます.
    まず言いたいのは、私たちが今まで学んだ知識からsuperを実現することは不可能です.
    考えてみます.これはどういう原理ですか?オブジェクト方法が実行されると、現在のオブジェクトはsuperとして動作する.thisを呼び出したら、どうやってsuper.method()を検索しますか?容易に考えられます.現在のオブジェクトの原型からmethodを取り出す必要があります.技術的には、私たち(またはJavaScriptエンジン)はこの点ができますか?methodの「Prottype」から方法を得ることができるかもしれません.thisと同じです.不幸なことに、これはだめです.
    簡単のためにクラスを使わずに普通のオブジェクトを使ってみましょう.
    ここで、this .__ proto __.methodは、親オブジェクトのrabbit.eat()方法を呼び出す.
    let animal = {
      name: "Animal",
      eat() {
        alert(`${this.name} eats.`);
      }
    };
    
    let rabbit = {
      __proto__: animal,
      name: "Rabbit",
      eat() {
    
        // that's how super.eat() could presumably work
        this.__proto__.eat.call(this); // (*)
    
      }
    };
    
    rabbit.eat(); // Rabbit eats.
    animal.eat()の行では、プロトタイプ((*))からanimalを取り出し、現在のオブジェクトのコンテキストで呼び出します.eatはここで重要です..call(this)だけを書くとthis .__ proto __.eat()の呼び出しオブジェクトは現在のオブジェクトではなくeatです.
    以上のコードのanimalは正しいです.
    しかし、もう一つのオブジェクトを原型チェーンに追加して、事故が起きます.
    let animal = {
      name: "Animal",
      eat() {
        alert(`${this.name} eats.`);
      }
    };
    
    let rabbit = {
      __proto__: animal,
      eat() {
        // ...bounce around rabbit-style and call parent (animal) method
        this.__proto__.eat.call(this); // (*)
      }
    };
    
    let longEar = {
      __proto__: rabbit,
      eat() {
        // ...do something with long ears and call parent (rabbit) method
        this.__proto__.eat.call(this); // (**)
      }
    };
    
    
    longEar.eat(); // Error: Maximum call stack size exceeded
    
    おっと、しまった呼び出しalertはエラーです.
    この原因は一目ではよく見えないかもしれませんが、追跡すればlongEar.eat()で呼び出されます.longEar.eat()および(*)の2つの行において、(**)の値は現在のオブジェクトである(this).ポイントが来ました.すべての方法は現在のオブジェクトを原型や他のものではなくlongEarとします.
    したがって、2行thisおよび(*)において、(**)の値はいずれもthis.__ proto__である.彼らはみんなrabbitを呼び出して、このように無限に循環し続けます.
    状況は図の通りです
    1.rabbit.eatの中でlongEar.eat()行に(**)が呼び出され、rabbit.eatが呼び出された.
    // inside longEar.eat() we have this = longEar
    this.__proto__.eat.call(this) // (**)
    // becomes
    longEar.__proto__.eat.call(this)
    // that is
    rabbit.eat.call(this);
    2.次にthis = longEarrabbit.eat行において、私達はプロトタイプチェーンの次の層に伝えたいですが、(*)this = longEarthis .__ proto __.eatです.
    // inside rabbit.eat() we also have this = longEar
    this.__proto__.eat.call(this) // (*)
    // becomes
    longEar.__proto__.eat.call(this)
    // or (again)
    rabbit.eat.call(this);
  • …したがって、 rabbit.eatは無限ループで移動し、次の層に入ることができない.
  • この問題は簡単には解決できません.rabbit.eat解決策を提供するために、JavaScriptは関数として特殊な内部属性を追加しました.
    関数がクラスまたはオブジェクト方法として指定されると、this属性はオブジェクトとなる.
    これは実際にunbind関数の思想に違反しています.方法はそれらの対象を覚えています.[[HomeObject]]は変更されないので、これは永久bindである.だから、JavaScriptでは大きな変化があります.
    しかし、このような変更は安全です.[[HomeObject]]は、[[HomeObject]]において次の層のプロトタイプを取得するためにのみ使用される.ですから、互換性は壊れません.
    どうやって[[HomeObject]]で動作するかを見てみましょう.
    let animal = {
      name: "Animal",
      eat() {         // [[HomeObject]] == animal
        alert(`${this.name} eats.`);
      }
    };
    
    let rabbit = {
      __proto__: animal,
      name: "Rabbit",
      eat() {         // [[HomeObject]] == rabbit
        super.eat();
      }
    };
    
    let longEar = {
      __proto__: rabbit,
      name: "Long Ear",
      eat() {         // [[HomeObject]] == longEar
        super.eat();
      }
    };
    
    
    longEar.eat();  // Long Ear eats.
    
    各方法は、内部[[HomeObject]]属性においてそのオブジェクトを記憶する.次いでsuperは、プロトタイプを解析するためにそれを使用する.
    クラスおよび一般オブジェクトにおいて定義された方法は、superを定義しているが、オブジェクトについては、[[HomeObject]]ではなくsuperを使用しなければならない.
    以下の例では、非方法文法(non-method syntax)を用いて比較する.これは[[HomeObject]]属性が設定されていません.継承も機能しません.
    let animal = {
      eat: function() { // should be the short syntax: eat() {...}
        // ...
      }
    };
    
    let rabbit = {
      __proto__: animal,
      eat: function() {
        super.eat();
      }
    };
    
    
    rabbit.eat();  // Error calling super (because there's no [[HomeObject]])
    
    静的方法と継承method()文法も静的属性の継承をサポートしている.
    たとえば:
    class Animal {
    
      constructor(name, speed) {
        this.speed = speed;
        this.name = name;
      }
    
      run(speed = 0) {
        this.speed += speed;
        alert(`${this.name} runs with speed ${this.speed}.`);
      }
    
      static compare(animalA, animalB) {
        return animalA.speed - animalB.speed;
      }
    
    }
    
    // Inherit from Animal
    class Rabbit extends Animal {
      hide() {
        alert(`${this.name} hides!`);
      }
    }
    
    let rabbits = [
      new Rabbit("White Rabbit", 10),
      new Rabbit("Black Rabbit", 5)
    ];
    
    rabbits.sort(Rabbit.compare);
    
    rabbits[0].run(); // Black Rabbit runs with speed 5.
    "method: function()"を呼び出すことができます.継承された[[HomeObject]]が呼び出されると仮定します.
    どうやって働いていますか?原型を再び使う.あなたが推測しているように、extensはclassRabbit.compareAnimal.compareに引用されている.
    したがって、Rabbit関数は、現在Animal関数を継承している.[Prototype]は、RabbitAnimalに引用しています.
    ここを見てください
    class Animal {}
    class Rabbit extends Animal {}
    
    // for static propertites and methods
    alert(Rabbit.__proto__ === Animal); // true
    
    // and the next step is Function.prototype
    alert(Animal.__proto__ === Function.prototype); // true
    
    // that's in addition to the "normal" prototype chain for object methods
    alert(Rabbit.prototype.__proto__ === Animal.prototype);
    このようにAnimalは、Function.prototypeのすべての静的方法にアクセスすることができる.
    内蔵オブジェクトに静的な継承はありません.
    なお、内蔵クラスには静的[[Prototype]]参照がない.例えば、extendRabbitAnimalなどの方法を持っているが、[[Prototype]]Objectはそれらを継承しない.Object.definePropertyおよびObject.keysの構成:ArrayDateの間には無関係に独立して存在していますが、DateObjectに継承されています.これだけです.
    この原因はJavaScriptが設計初期にclass文法を使って静的な方法を継承することを考慮していなかったからです.
    原生拡張
    Aray、Mapなどの内蔵類も拡張できます.
    一例として、Dateは、自生Objectを継承する.
    // add one more method to it (can do more)
    class PowerArray extends Array {
      isEmpty() {
        return this.length === 0;
      }
    }
    
    let arr = new PowerArray(1, 2, 5, 10, 50);
    alert(arr.isEmpty()); // false
    
    let filteredArr = arr.filter(item => item >= 10);
    alert(filteredArr); // 10, 50
    alert(filteredArr.isEmpty()); // false
    とても面白いことに注意してください.Date.prototypeのように、Object.prototypeおよび他の内蔵方法は、新しい継承タイプのオブジェクトを返す.彼らはPowerArray属性に頼ってこのようにします.
    上記の例では、
    arr.constructor === PowerArray
    したがって、Arrayを呼び出すと、filterのように新しい結果配列が自動的に作成され、したがって、我々は引き続きPowerArayの方法を使用することができる.
    私たちはこのような行動をカスタマイズすることもできます.静的なgetter mapがある場合、新しいオブジェクトに使用されるconstructorに戻る.
    以下の例では、constructorの存在により、arr.filter()new PowerArrayなどの内蔵方法は、通常の配列に戻る.
    class PowerArray extends Array {
      isEmpty() {
        return this.length === 0;
      }
    
    
      // built-in methods will use this as the constructor
      static get [Symbol.species]() {
        return Array;
      }
    
    }
    
    let arr = new PowerArray(1, 2, 5, 10, 50);
    alert(arr.isEmpty()); // false
    
    // filter creates new array using arr.constructor[Symbol.species] as constructor
    let filteredArr = arr.filter(item => item >= 10);
    
    
    // filteredArr is not PowerArray, but Array
    
    alert(filteredArr.isEmpty()); // Error: filteredArr.isEmpty is not a function
    他のkeyでSymbol.speciesを使用してもいいです.結果値を剥離するための無駄な方法、または他の方法を追加するために使用できます.