JavaScriptのプロトタイプと継承を理解する
序章
JavaScriptはプロトタイプベースの言語です。つまり、オブジェクトのプロパティとメソッドは、複製および拡張できる一般化されたオブジェクトを介して共有できます。 これはプロトタイプ継承と呼ばれ、クラス継承とは異なります。 PHP、Python、Javaなどの他の著名な言語はクラスベースの言語であり、代わりにクラスをオブジェクトの青写真として定義するため、人気のあるオブジェクト指向プログラミング言語の中で、JavaScriptは比較的ユニークです。
このチュートリアルでは、オブジェクトプロトタイプとは何か、およびコンストラクター関数を使用してプロトタイプを新しいオブジェクトに拡張する方法を学習します。 また、継承とプロトタイプチェーンについても学びます。
JavaScriptプロトタイプ
JavaScriptでのオブジェクトの理解では、オブジェクトのデータ型、オブジェクトの作成方法、およびオブジェクトのプロパティへのアクセスと変更の方法について説明しました。 次に、プロトタイプを使用してオブジェクトを拡張する方法を学習します。
JavaScriptのすべてのオブジェクトには、という内部プロパティがあります [[Prototype]]
. これは、新しい空のオブジェクトを作成することで実証できます。
let x = {};
これは通常オブジェクトを作成する方法ですが、これを実現する別の方法はオブジェクトコンストラクターを使用することです。 let x = new Object()
.
囲む二重角かっこ [[Prototype]]
これは内部プロパティであり、コードで直接アクセスできないことを意味します。
を見つけるには [[Prototype]]
この新しく作成されたオブジェクトのうち、 getPrototypeOf()
方法。
Object.getPrototypeOf(x);
出力は、いくつかの組み込みプロパティとメソッドで構成されます。
Output{constructor: ƒ, __defineGetter__: ƒ, __defineSetter__: ƒ, …}
を見つける別の方法 [[Prototype]]
を通してです __proto__
財産。 __ proto __ は、内部を公開するプロパティです [[Prototype]]
オブジェクトの。
注意することが重要です .__proto__
はレガシー機能であり、本番コードでは使用しないでください。また、最新のすべてのブラウザに存在するわけではありません。 ただし、この記事全体で説明目的で使用できます。
x.__proto__;
出力は、使用した場合と同じになります getPrototypeOf()
.
Output{constructor: ƒ, __defineGetter__: ƒ, __defineSetter__: ƒ, …}
JavaScriptのすべてのオブジェクトに [[Prototype]]
2つ以上のオブジェクトをリンクする方法を作成するためです。
作成するオブジェクトには [[Prototype]]
、などの組み込みオブジェクトも同様です Date
と Array
. あるオブジェクトから別のオブジェクトへのこの内部プロパティへの参照は、 prototype
このチュートリアルの後半で説明するように、プロパティ。
プロトタイプの継承
オブジェクトのプロパティまたはメソッドにアクセスしようとすると、JavaScriptは最初にオブジェクト自体を検索し、見つからない場合はオブジェクトのを検索します。 [[Prototype]]
. オブジェクトとその両方を調べた後の場合 [[Prototype]]
それでも一致するものが見つからない場合、JavaScriptはリンクされたオブジェクトのプロトタイプをチェックし、プロトタイプチェーンの最後に到達するまで検索を続けます。
プロトタイプチェーンの最後には、Object.prototypeがあります。 すべてのオブジェクトは、Objectのプロパティとメソッドを継承します。 チェーンの終わりを超えて検索しようとすると、 null
.
この例では、 x
から継承する空のオブジェクトです Object
. x
任意のプロパティまたはメソッドを使用できます Object
持っている、など toString()
.
x.toString();
Output[object Object]
このプロトタイプチェーンの長さは1リンクだけです。 x
-> Object
. 私たちはこれを知っています、なぜなら私たちが2つを連鎖させようとすると [[Prototype]]
プロパティを一緒に、それはなります null
.
x.__proto__.__proto__;
Outputnull
別のタイプのオブジェクトを見てみましょう。 JavaScriptでの配列の操作の経験がある場合は、次のような多くの組み込みメソッドがあることをご存知でしょう。 pop()
と push()
. 新しい配列を作成するときにこれらのメソッドにアクセスできる理由は、作成するすべての配列が上のプロパティとメソッドにアクセスできるためです。 Array.prototype
.
新しい配列を作成することでこれをテストできます。
let y = [];
配列コンストラクターとしても記述できることを覚えておいてください。 let y = new Array()
.
見てみると [[Prototype]]
新しいの y
配列の場合、より多くのプロパティとメソッドがあることがわかります。 x
物体。 それはからすべてを継承しています Array.prototype
.
y.__proto__;
[constructor: ƒ, concat: ƒ, pop: ƒ, push: ƒ, …]
あなたは気づくでしょう constructor
に設定されているプロトタイプのプロパティ Array()
. The constructor
プロパティは、オブジェクトのコンストラクター関数を返します。これは、関数からオブジェクトを構築するために使用されるメカニズムです。
この場合、プロトタイプチェーンが長くなるため、2つのプロトタイプをチェーンできます。 のように見えます y
-> Array
-> Object
.
y.__proto__.__proto__;
Output{constructor: ƒ, __defineGetter__: ƒ, __defineSetter__: ƒ, …}
このチェーンは現在参照しています Object.prototype
. 内部をテストできます [[Prototype]]
に対して prototype
コンストラクター関数のプロパティを使用して、同じものを参照していることを確認します。
y.__proto__ === Array.prototype; // true
y.__proto__.__proto__ === Object.prototype; // true
使用することもできます isPrototypeOf()
これを達成するための方法。
Array.prototype.isPrototypeOf(y); // true
Object.prototype.isPrototypeOf(Array); // true
使用できます instanceof
オペレーターが prototype
コンストラクターのプロパティは、オブジェクトのプロトタイプチェーン内の任意の場所に表示されます。
y instanceof Array; // true
要約すると、すべてのJavaScriptオブジェクトには非表示の内部があります [[Prototype]]
プロパティ( __proto__
一部のブラウザでは)。 オブジェクトは拡張可能であり、プロパティとメソッドを継承します [[Prototype]]
彼らのコンストラクターの。
これらのプロトタイプはチェーン化することができ、追加の各オブジェクトはチェーン全体ですべてを継承します。 チェーンはで終わります Object.prototype
.
コンストラクター関数
コンストラクター関数は、新しいオブジェクトを作成するために使用される関数です。 new演算子は、コンストラクター関数に基づいて新しいインスタンスを作成するために使用されます。 次のような組み込みのJavaScriptコンストラクターを見てきました。 new Array()
と new Date()
、ただし、新しいオブジェクトを作成するための独自のカスタムテンプレートを作成することもできます。
例として、非常にシンプルなテキストベースのロールプレイングゲームを作成しているとしましょう。 ユーザーはキャラクターを選択してから、戦士、ヒーラー、泥棒など、どのキャラクタークラスを使用するかを選択できます。
各キャラクターは、名前、レベル、ヒットポイントなど、多くの特性を共有するため、コンストラクターをテンプレートとして作成することは理にかなっています。 ただし、各キャラクタークラスの能力は大きく異なる可能性があるため、各キャラクターが自分の能力にのみアクセスできるようにする必要があります。 プロトタイプの継承とコンストラクターを使用してこれを実現する方法を見てみましょう。
まず、コンストラクター関数は単なる通常の関数です。 インスタンスによって呼び出されると、コンストラクタになります。 new
キーワード。 JavaScriptでは、慣例によりコンストラクター関数の最初の文字を大文字にします。
// Initialize a constructor function for a new Hero
function Hero(name, level) {
this.name = name;
this.level = level;
}
と呼ばれるコンストラクター関数を作成しました Hero
2つのパラメータを使用: name
と level
. すべてのキャラクターには名前とレベルがあるので、新しいキャラクターごとにこれらのプロパティを持つことは理にかなっています。 The this
キーワードは作成された新しいインスタンスを参照するため、 this.name
に name
パラメータは、新しいオブジェクトに name
プロパティセット。
これで、で新しいインスタンスを作成できます new
.
let hero1 = new Hero('Bjorn', 1);
私たちが慰めれば hero1
、新しいプロパティが期待どおりに設定された新しいオブジェクトが作成されたことがわかります。
OutputHero {name: "Bjorn", level: 1}
今、私たちが [[Prototype]]
の hero1
、私たちは見ることができるようになります constructor
なので Hero()
. (これはと同じ入力を持っていることを覚えておいてください hero1.__proto__
、ただし、使用するのに適切な方法です。)
Object.getPrototypeOf(hero1);
Outputconstructor: ƒ Hero(name, level)
コンストラクターでメソッドを定義せず、プロパティのみを定義していることに気付くかもしれません。 効率とコードの可読性を高めるために、プロトタイプでメソッドを定義することはJavaScriptの一般的な方法です。
メソッドを追加できます Hero
を使用して prototype
. 作成します greet()
方法。
...
// Add greet method to the Hero prototype
Hero.prototype.greet = function () {
return `${this.name} says hello.`;
}
以来 greet()
の中に prototype
の Hero
、 と hero1
のインスタンスです Hero
、メソッドは hero1
.
hero1.greet();
Output"Bjorn says hello."
あなたが検査する場合 [[Prototype]]
ヒーローの、あなたは見るでしょう greet()
現在利用可能なオプションとして。
これは良いことですが、ヒーローが使用するキャラクタークラスを作成したいと思います。 すべてのクラスのすべての能力を Hero
クラスが異なれば能力も異なるため、コンストラクター。 新しいコンストラクター関数を作成したいが、それらを元のコンストラクター関数に接続したい Hero
.
call()メソッドを使用して、あるコンストラクターから別のコンストラクターにプロパティをコピーできます。 WarriorとHealerのコンストラクターを作成しましょう。
...
// Initialize Warrior constructor
function Warrior(name, level, weapon) {
// Chain constructor with call
Hero.call(this, name, level);
// Add a new property
this.weapon = weapon;
}
// Initialize Healer constructor
function Healer(name, level, spell) {
Hero.call(this, name, level);
this.spell = spell;
}
両方の新しいコンストラクターは、次のプロパティを持ちます。 Hero
といくつかのunqiueのもの。 追加します attack()
方法 Warrior
、 そしてその heal()
方法 Healer
.
...
Warrior.prototype.attack = function () {
return `${this.name} attacks with the ${this.weapon}.`;
}
Healer.prototype.heal = function () {
return `${this.name} casts ${this.spell}.`;
}
この時点で、利用可能な2つの新しいキャラクタークラスを使用してキャラクターを作成します。
const hero1 = new Warrior('Bjorn', 1, 'axe');
const hero2 = new Healer('Kanin', 1, 'cure');
hero1
現在、 Warrior
新しいプロパティで。
OutputWarrior {name: "Bjorn", level: 1, weapon: "axe"}
に設定した新しいメソッドを使用できます Warrior
プロトタイプ。
hero1.attack();
Console"Bjorn attacks with the axe."
しかし、プロトタイプチェーンのさらに下流でメソッドを使用しようとするとどうなりますか?
hero1.greet();
OutputUncaught TypeError: hero1.greet is not a function
プロトタイプのプロパティとメソッドは、使用時に自動的にリンクされません call()
コンストラクターをチェーンします。 我々は使用するだろう Object.setPropertyOf()
のプロパティをリンクするには Hero
コンストラクター Warrior
と Healer
コンストラクター。追加のメソッドの前に配置してください。
...
Object.setPrototypeOf(Warrior.prototype, Hero.prototype);
Object.setPrototypeOf(Healer.prototype, Hero.prototype);
// All other prototype methods added below
...
これで、次のプロトタイプメソッドを正常に使用できます。 Hero
のインスタンスで Warrior
また Healer
.
hero1.greet();
Output"Bjorn says hello."
これがキャラクター作成ページの完全なコードです。
// Initialize constructor functions
function Hero(name, level) {
this.name = name;
this.level = level;
}
function Warrior(name, level, weapon) {
Hero.call(this, name, level);
this.weapon = weapon;
}
function Healer(name, level, spell) {
Hero.call(this, name, level);
this.spell = spell;
}
// Link prototypes and add prototype methods
Object.setPrototypeOf(Warrior.prototype, Hero.prototype);
Object.setPrototypeOf(Healer.prototype, Hero.prototype);
Hero.prototype.greet = function () {
return `${this.name} says hello.`;
}
Warrior.prototype.attack = function () {
return `${this.name} attacks with the ${this.weapon}.`;
}
Healer.prototype.heal = function () {
return `${this.name} casts ${this.spell}.`;
}
// Initialize individual character instances
const hero1 = new Warrior('Bjorn', 1, 'axe');
const hero2 = new Healer('Kanin', 1, 'cure');
このコードを使用して、 Hero
基本プロパティを持つコンストラクター、と呼ばれる2つの文字コンストラクターを作成しました Warrior
と Healer
元のコンストラクターから、プロトタイプにメソッドを追加し、個々の文字インスタンスを作成しました。
結論
JavaScriptはプロトタイプベースの言語であり、他の多くのオブジェクト指向言語が使用する従来のクラスベースのパラダイムとは機能が異なります。
このチュートリアルでは、JavaScriptでプロトタイプがどのように機能するか、および非表示を介してオブジェクトのプロパティとメソッドをリンクする方法を学びました。 [[Prototype]]
すべてのオブジェクトが共有するプロパティ。 また、カスタムコンストラクター関数を作成する方法と、プロトタイプの継承がプロパティとメソッドの値を渡すためにどのように機能するかについても学びました。