JavaScriptのプロトタイプと継承について

前書き

JavaScriptは*プロトタイプベースの言語*です。つまり、オブジェクトのプロパティとメソッドは、複製および拡張する機能を持つ一般化されたオブジェクトを介して共有できます。 これはプロトタイプ継承と呼ばれ、クラス継承とは異なります。 人気のあるオブジェクト指向プログラミング言語の中で、JavaScriptは比較的ユニークです。PHP、Python、Javaなどの他の著名な言語はクラスベースの言語であり、代わりにクラスをオブジェクトの設計図として定義するからです。

このチュートリアルでは、オブジェクトプロトタイプとは何か、コンストラクタ関数を使用してプロトタイプを新しいオブジェクトに拡張する方法を学習します。 また、継承とプロトタイプチェーンについても学習します。

JavaScriptプロトタイプ

Understanding Objects in JavaScriptで、オブジェクトのデータ型、オブジェクトの作成方法、およびオブジェクトへのアクセスと変更の方法について説明しました。プロパティ。 次に、プロトタイプを使用してオブジェクトを拡張する方法を学習します。

JavaScriptのすべてのオブジェクトには、 `+ +`という内部プロパティがあります。 これは、新しい空のオブジェクトを作成することで実証できます。

let x = {};

これは通常オブジェクトを作成する方法ですが、これを実現する別の方法はオブジェクトコンストラクターを使用することです。`+ let x = new Object()+ `。

この新しく作成されたオブジェクトの `+ `を見つけるには、 ` getPrototypeOf()+`メソッドを使用します。

Object.getPrototypeOf(x);

出力は、いくつかの組み込みプロパティとメソッドで構成されます。

Output{constructor: ƒ, __defineGetter__: ƒ, __defineSetter__: ƒ, …}

+ +`を見つける別の方法は、 `+ proto +`プロパティを使用することです。 https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/proto [+ proto `]は、内部の ` +`を公開するプロパティですオブジェクトの。

x.__proto__;

出力は、 `+ getPrototypeOf()+`を使用した場合と同じになります。

Output{constructor: ƒ, __defineGetter__: ƒ, __defineSetter__: ƒ, …}

JavaScript内のすべてのオブジェクトが `+ +`を持っていることが重要です。2つ以上のオブジェクトをリンクする方法を作成するからです。

作成するオブジェクトには、 + Date _`や + Array I`などの組み込みオブジェクトと同様に、 `+ `があります。 このチュートリアルの後半で説明するように、 ` prototype +`プロパティを使用して、あるオブジェクトから別のオブジェクトへこの内部プロパティを参照できます。

プロトタイプ継承

オブジェクトのプロパティまたはメソッドにアクセスしようとすると、JavaScriptはまずオブジェクト自体を検索し、見つからない場合はオブジェクトの `+ `を検索します。 オブジェクトとその ` +`の両方を調べても一致が見つからない場合、JavaScriptはリンクされたオブジェクトのプロトタイプをチェックし、プロトタイプチェーンの最後に到達するまで検索を続けます。

プロトタイプチェーンの最後はhttps://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/prototype [+ Object.prototype +]です。 すべてのオブジェクトはhttps://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object [+ Object +]のプロパティとメソッドを継承します。 チェーンの終わりを超えて検索しようとすると、 `+ null +`になります。

この例では、 `+ x `は ` Object `を継承する空のオブジェクトです。 ` x `は、 ` toString()`など、 ` Object +`が持つプロパティまたはメソッドを使用できます。

x.toString();
Output[object Object]

このプロトタイプチェーンは、1リンクのみです。 + x ++ Object +。 これは、2つの `+ `プロパティを連結しようとすると、 ` null +`になるためです。

x.__proto__.__proto__;
Outputnull

別の種類のオブジェクトを見てみましょう。 JavaScriptで配列を操作するの経験がある場合は、 + popなどの多くの組み込みメソッドがあることがわかります。 ()+ `および + push()+ `。 新しい配列を作成するときにこれらのメソッドにアクセスできるのは、作成する配列が `+ Array.prototype +`のプロパティとメソッドにアクセスできるためです。

これをテストするには、新しい配列を作成します。

let y = [];

配列コンストラクター、 `+ let y = new Array()+`として記述することもできます。

新しい `+ y `配列の ` `を見ると、 ` x `オブジェクトよりも多くのプロパティとメソッドがあることがわかります。 ` Array.prototype +`からすべてを継承しています。

y.__proto__;
[constructor: ƒ, concat: ƒ, pop: ƒ, push: ƒ, …]

`+ Array()`に設定されたプロトタイプの ` constructor `プロパティに気付くでしょう。 ` constructor +`プロパティは、オブジェクトのコンストラクター関数を返します。これは、関数からオブジェクトを構築するために使用されるメカニズムです。

この場合、プロトタイプチェーンが長くなるため、2つのプロトタイプを連結できます。 + y ++ Array→` + Object N`のように見えます。

y.__proto__.__proto__;
Output{constructor: ƒ, __defineGetter__: ƒ, __defineSetter__: ƒ, …}

このチェーンは現在、「+ Object.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オブジェクトには、隠された内部の `+ `プロパティがあります(一部のブラウザでは ` proto `を通じて公開される場合があります)。 オブジェクトは拡張でき、コンストラクターの ` +`のプロパティとメソッドを継承します。

これらのプロトタイプはチェーン化でき、追加の各オブジェクトはチェーン全体ですべてを継承します。 チェーンは「+ Object.prototype +」で終わります。

コンストラクター関数

*コンストラクター関数*は、新しいオブジェクトを構築するために使用される関数です。 https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/new [+ new + operator]は、コンストラクター関数に基づいて新しいインスタンスを作成するために使用されます。 `+ new Array()`や ` new Date()+`などの組み込みJavaScriptコンストラクターを見てきましたが、独自のカスタムテンプレートを作成して新しいオブジェクトを構築することもできます。

例として、非常にシンプルなテキストベースのロールプレイングゲームを作成しているとしましょう。 ユーザーはキャラクターを選択し、戦士、ヒーラー、泥棒など、どのキャラクタークラスを使用するかを選択できます。

各キャラクターは、名前、レベル、ヒットポイントなど、多くの特性を共有するため、コンストラクターをテンプレートとして作成することは理にかなっています。 ただし、各キャラクタークラスの能力は大きく異なる可能性があるため、各キャラクターが自分の能力にのみアクセスできるようにする必要があります。 プロトタイプの継承とコンストラクターでこれを達成する方法を見てみましょう。

まず、コンストラクター関数は単なる通常の関数です。 `+ new +`キーワードを持つインスタンスによって呼び出されると、コンストラクターになります。 JavaScriptでは、慣例によりコンストラクター関数の最初の文字を大文字にします。

characterSelect.js

// Initialize a constructor function for a new Hero
function Hero(name, level) {
 this.name = name;
 this.level = level;
}

+ Hero +`という2つのパラメーターを持つコンストラクター関数を作成しました: `+ name +`と `+ level +。 すべてのキャラクターには名前とレベルがあるため、新しいキャラクターがこれらのプロパティを持つことは理にかなっています。 `+ this `キーワードは作成された新しいインスタンスを参照するため、 ` this.name `を ` name `パラメーターに設定すると、新しいオブジェクトに ` name +`プロパティが設定されます。

これで、 `+ new +`で新しいインスタンスを作成できます。

let hero1 = new Hero('Bjorn', 1);

`+ hero1 +`をコンソールアウトすると、期待どおりに新しいプロパティが設定された新しいオブジェクトが作成されます。

OutputHero {name: "Bjorn", level: 1}

これで、 `+ hero1 `の ` `を取得すると、 ` constructor `を ` Hero()`として見ることができます。 (これは ` hero1 . proto +`と同じ入力を持ちますが、使用するのに適切な方法です。)

Object.getPrototypeOf(hero1);
Outputconstructor: ƒ Hero(name, level)

コンストラクタのメソッドではなく、プロパティのみを定義していることに気づくかもしれません。 JavaScriptの一般的な慣習では、プロトタイプのメソッドを定義して、効率とコードの読みやすさを向上させています。

`+ prototype `を使用して、 ` Hero `にメソッドを追加できます。 ` greet()+`メソッドを作成します。

characterSelect.js

...
// Add greet method to the Hero prototype
Hero.prototype.greet = function () {
 return `${this.name} says hello.`;
}

`+ greet()`は ` Hero `の ` prototype `にあり、 ` hero1 `は ` Hero `のインスタンスであるため、このメソッドは ` hero1 +`で使用できます。

hero1.greet();
Output"Bjorn says hello."

Heroの `+ `を調べると、利用可能なオプションとして ` greet()+`が表示されます。

これは良いことですが、今度はヒーローが使用するキャラクタークラスを作成したいと思います。 クラスごとに能力が異なるため、すべてのクラスのすべての能力を `+ Hero `コンストラクターに入れても意味がありません。 新しいコンストラクター関数を作成したいが、それらを元の ` Hero +`に接続したい。

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/call [+ call()+]メソッドを使用して、あるコンストラクターから別のコンストラクターにプロパティをコピーできます。コンストラクタ。 戦士とヒーラーのコンストラクターを作成しましょう。

characterSelect.js

...
// 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`のプロパティといくつかのユニークなものがあります。 `+ attar()`メソッドを ` Warrior `に追加し、 ` heal()`メソッドを ` Healer +`に追加します。

characterSelect.js

...
Warrior.prototype.attack = function () {
 return `${this.name} attacks with the ${this.weapon}.`;
}

Healer.prototype.heal = function () {
 return `${this.name} casts ${this.spell}.`;
}

この時点で、2つの新しいキャラクタークラスを使用してキャラクターを作成します。

characterSelect.js

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.create()+`を使用してプロトタイプをリンクし、追加のメソッドが作成されてプロトタイプに追加される前に必ず配置します。

characterSelect.js

...
Warrior.prototype = Object.create(Hero.prototype);
Healer.prototype = Object.create(Hero.prototype);

// All other prototype methods added below
...

これで、 + Warrior And + Healer`のインスタンスで `+ Hero +`のプロトタイプメソッドを正常に使用できます。

hero1.greet();
Output"Bjorn says hello."

これが、キャラクター作成ページの完全なコードです。

characterSelect.js

// 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
Warrior.prototype = Object.create(Hero.prototype);
Healer.prototype = Object.create(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 」クラスを作成し、元のコンストラクターから「 Warrior 」および「 Healer +」という2つの文字クラスを作成し、プロトタイプにメソッドを追加し、個々の文字インスタンスを作成しました。

結論

JavaScriptはプロトタイプベースの言語であり、他の多くのオブジェクト指向言語が使用する従来のクラスベースのパラダイムとは機能が異なります。

このチュートリアルでは、プロトタイプがJavaScriptでどのように機能するか、およびすべてのオブジェクトが共有する非表示の `+ +`プロパティを介してオブジェクトのプロパティとメソッドをリンクする方法を学びました。 また、カスタムコンストラクター関数を作成する方法と、プロトタイプ値の継承がどのようにプロパティとメソッドの値を渡すかを学習しました。