ES 6クラス継承とsuper
13204 ワード
テキストhttps://javascript.info/class...
クラス継承とスーパー
クラスは他のクラスからextensできます.これはいい文法です.技術的には原型に基づいて継承します.
オブジェクトを継承するには、
この
したがって、
Class文法の‘extens’の後に続くのは1つのクラスを指定するだけではなくて、更に表現式であることができます.
例えば、親クラスを生成する関数:
高級プログラミングモードに対して,多くの条件に従って関数を用いて生成したクラスを使用すると,これは有用である.
書き換え方法
次のステップに進み、もう一つの方法を書き直しましょう.これまで
Classはこのためには、 は、 例えば、ウサギは
矢印関数は
arrow-functionsの章で述べたように、矢印関数は
外部関数から
コンストラクタにとってはちょっと苦手なtrickyです.
今まで、
仕様によれば、1つのクラスが他のクラスに拡張され、
次にカスタムコンストラクタを
簡単には、クラスを継承する構造関数は、
どうしてですか?これはどういう状況ですか?えっと、この要求は確かにおかしいです.
詳細を検討して、その原因を本当に理解させます.
JavaScriptでは、他のクラスの構造関数を継承するのが特殊です.継承クラスでは、対応する構造関数を特殊な内部属性
違いは:普通のコンストラクタが動作すると、thisとして空のオブジェクトを作成して実行します. しかし、派生した構造関数が動作するときは、上記とは違って、父の構造関数がこの仕事を完成することを期待している. したがって、私たちが自分たちの構造関数を構築している場合、
まず言いたいのは、私たちが今まで学んだ知識からsuperを実現することは不可能です.
考えてみます.これはどういう原理ですか?オブジェクト方法が実行されると、現在のオブジェクトは
簡単のためにクラスを使わずに普通のオブジェクトを使ってみましょう.
ここで、
以上のコードの
しかし、もう一つのオブジェクトを原型チェーンに追加して、事故が起きます.
この原因は一目ではよく見えないかもしれませんが、追跡すれば
したがって、2行
状況は図の通りです
1.…したがって、 この問題は簡単には解決できません.
関数がクラスまたはオブジェクト方法として指定されると、
これは実際にunbind関数の思想に違反しています.方法はそれらの対象を覚えています.
しかし、このような変更は安全です.
どうやって
クラスおよび一般オブジェクトにおいて定義された方法は、
以下の例では、非方法文法(non-method syntax)を用いて比較する.これは
たとえば:
どうやって働いていますか?原型を再び使う.あなたが推測しているように、extensは
したがって、
ここを見てください
内蔵オブジェクトに静的な継承はありません.
なお、内蔵クラスには静的
この原因はJavaScriptが設計初期にclass文法を使って静的な方法を継承することを考慮していなかったからです.
原生拡張
Aray、Mapなどの内蔵類も拡張できます.
一例として、
上記の例では、
私たちはこのような行動をカスタマイズすることもできます.静的なgetter
以下の例では、
クラス継承とスーパー
クラスは他のクラスからextensできます.これはいい文法です.技術的には原型に基づいて継承します.
オブジェクトを継承するには、
{..}
の前にextends
と親オブジェクトを指定する必要があります.この
Rabbit
はAnimal
から継承されている.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')の戻りの結果を引き継いでいる.高級プログラミングモードに対して,多くの条件に従って関数を用いて生成したクラスを使用すると,これは有用である.
書き換え方法
次のステップに進み、もう一つの方法を書き直しましょう.これまで
Rabbit
はAnimal
から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!
次に、stop
のRabbit
方法は、stop
を介して親タイプの方法を呼び出す.矢印関数は
super.stop()
ではありません.arrow-functionsの章で述べたように、矢印関数は
super
にありません.外部関数から
super
を取得する.たとえば:class Rabbit extends Animal {
stop() {
setTimeout(() => super.stop(), 1000); // call parent stop after 1sec
}
}
矢印関数のsuper
はsuper
と同じであるので、期待どおりに動作する.ここで普通の関数を使うと、エラーが発生します.// 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
としてマークしている.違いは:
[[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 = longEar
のrabbit.eat
行において、私達はプロトタイプチェーンの次の層に伝えたいですが、(*)
、this = longEar
はthis .__ 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は
class
にRabbit.compare
のAnimal.compare
に引用されている.したがって、
Rabbit
関数は、現在Animal
関数を継承している.[Prototype]
は、Rabbit
のAnimal
に引用しています.ここを見てください
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]]
参照がない.例えば、extend
はRabbit
、Animal
などの方法を持っているが、[[Prototype]]
、Object
はそれらを継承しない.Object.defineProperty
およびObject.keys
の構成:Array
とDate
の間には無関係に独立して存在していますが、Date
はObject
に継承されています.これだけです.この原因は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
を使用してもいいです.結果値を剥離するための無駄な方法、または他の方法を追加するために使用できます.