プロトタイプは JavaScript オブジェクトが機能を互いに継承するメカニズムです。この記事ではプロトタイプチェーンの動作を説明し、prototype プロパティを使用して既存のコンストラクタにメソッドを追加する方法を見ていきます。

前提条件: JavaScript関数の理解、JavaScriptの基礎知識 (JavaScript の第一歩JavaScript の構成要素を参照)、OOJSの基礎 (JavaScript オブジェクトの基本を参照)
目的: JavaScriptのオブジェクトのプロトタイプ、プロトタイプチェーンの動作方法、prototypeプロパティに新しいメソッドを追加する方法を理解する。

プロトタイプベースの言語とは?

JavaScriptはよくプロトタイプベースの言語として記述されています。各オブジェクトにはプロトタイプオブジェクトがあり、メソッドとプロパティを継承するテンプレートオブジェクトとして機能します。 オブジェクトのプロトタイプオブジェクトはプロトタイプオブジェクトを持ち、そこからメソッドやプロパティを継承します。これはよくプロトタイプチェーンと呼ばれ、さまざまなオブジェクトが利用可能な他のオブジェクトで定義されたプロパティとメソッドを持つ理由を説明します。

正確には、プロパティとメソッドはオブジェクトのインスタンスではなく、オブジェクトのコンストラクタ関数のprototypeプロパティで定義されています。

JavaScriptでは、オブジェクトインスタンスとそのプロトタイプ(その__proto__プロパティで、コンストラクタのprototypeプロパティから派生しています)との間にリンクがあり、プロパティとメソッドはプロトタイプのチェーンを辿ることで見つけられます。

Note: オブジェクトのプロトタイプ(Object.getPrototypeOf(obj)、または廃止予定の__proto__プロパティを介して利用可能)とコンストラクタ関数のプロトタイププロパティとの間には区別があることを理解することが重要です。前者は各インスタンスのプロパティ、後者はコンストラクタのプロパティです。つまり、Object.getPrototypeOf(new Foobar())は、Foobar.prototypeと同じオブジェクトを参照します。

これを少し明確にするための例を見てみましょう。

プロトタイプオブジェクトの理解

ここでは、Person()コンストラクタの記述を終了した例に戻って、ブラウザにサンプルをロードします。前回の記事で作業していない場合は、oojs-class-further-exercises.html の例 (ソースコードも参照してください) を使用してください。

この例では、次のようにコンストラクタ関数を定義しています。

function Person(first, last, age, gender, interests) {
  
  // property and method definitions
  this.first = first;
  this.last = last;
//...
}

次に、このようなオブジェクトインスタンスを作成します。

var person1 = new Person('Bob', 'Smith', 32, 'male', ['music', 'skiing']);

JavaScriptコンソールに"person1."と入力すると、ブラウザがこのオブジェクトで利用可能なメンバ名でこれを自動補完しようとするはずです:

このリストでは nameagegenderinterestsbiogreetingなどPerson() (Person()がコンストラクタ) のperson1のプロトタイプオブジェクトに定義されているメンバーが表示されます。しかし、他のメンバー (watchvalueOfなど) も表示されます。これらは Person() のプロトタイプオブジェクトで定義されています。これはObjectです。 これはプロトタイプの連鎖作業を示しています。

では、実際にObjectで定義されているperson1のメソッドを呼び出すとどうなるでしょうか? 例えば:

person1.valueOf()

このメソッドは呼び出されたオブジェクトの値を返します。試してみてください! この場合、何が起こるか:

  • ブラウザは、person1オブジェクトがvalueOf()メソッドを利用できるかどうかを最初にチェックします
  • そうではないので、ブラウザはperson1オブジェクトのプロトタイプオブジェクト (Person()コンストラクタのプロトタイプ) にvalueOf()メソッドがあるかどうかを調べます
  • ブラウザは、Person()コンストラクタのプロトタイプオブジェクトのプロトタイプオブジェクト (Object()constructorprototype) に valueOf()メソッドがあるかどうかをチェックします。それは呼ばれ、すべてがうまく行きます!

Note: プロトタイプチェーンでメソッドとプロパティがオブジェクト間でコピーされていないことを繰り返しておきたいと思います。

Note: オブジェクトのプロトタイプオブジェクトに直接アクセスする正式な方法はありません。チェーン内のアイテム間の「リンク」は、JavaScript言語 (ECMAScript を参照) の仕様で[[prototype]]と呼ばれる内部プロパティで定義されています。しかし最新のブラウザでは、オブジェクトのコンストラクタのプロトタイプオブジェクトを含む __proto__ (どちらも2つのアンダースコアがあります) という名前のプロパティがあります。例えば、person1.__proto__person1.__proto__.__proto__を実行すると、コード内のチェーンの様子を見ることができます。

ECMAScript 2015 以降、Object.getPrototypeOf(obj) を介してオブジェクトのプロトタイプオブジェクトに間接的にアクセスできます。

prototypeプロパティ:継承されたメンバーが定義されている場所

では、継承されたプロパティとメソッドはどこに定義されているのでしょうか? Objectリファレンスページを見ると、左側に多数のプロパティとメソッドが表示されます。上のスクリーンショットでperson1オブジェクトで使用できた継承されたメンバーの数を超えています。いくつかは継承されており、一部は継承されていません。これはなぜでしょうか?

その答えは、継承されたものはprototypeプロパティ (サブネームスペースと呼ぶことができます) で定義されているものです。つまり、Object.prototype.で始まるもので、単にObject.で始まるものではありません。prototypeプロパティの値は基本的にプロパティとメソッドを格納するためのバケットであり、プロトタイプチェーンのさらに下にあるオブジェクトによって継承されることが期待されるオブジェクトです。

したがって、Object.prototype.watch()Object.prototype.valueOf()などは、コンストラクタから作成された新しいオブジェクトインスタンスを含め、Object.prototypeを継承するすべてのオブジェクトタイプで使用できます。

Object.is()Object.keys() およびprototypeバケット内で定義されていない他のメンバーは、Object.prototypeから継承したオブジェクトインスタンスまたはオブジェクト型に継承されません。これらは、Object()コンストラクタ自体で使用できるメソッド/プロパティです。

Note: これは奇妙に思えます - それ自体が関数であるコンストラクタでメソッドを定義するにはどうすればいいのでしょうか? そう、関数もオブジェクトの一種です。あなたが私たちを信じていない場合は、Function()コンストラクタリファレンスを参照してください。

  1. 既存のプロトタイププロパティを自分でチェックアウトすることができます。前の例に戻り、JavaScriptコンソールに次のように入力してみてください。
    Person.prototype
  2. 出力はあなたにはあまり表示されません - 結局のところ、私たちはカスタムコンストラクタのプロトタイプで何も定義していません!デフォルトでは、コンストラクタのprototypeは常に空になります。 次に、以下を試してみましょう:
    Object.prototype

Objectprototypeプロパティで定義された多数のメソッドが表示されます。これらのメソッドは、前述のようにObjectから継承したオブジェクトで使用できます。

JavaScript上のプロトタイプチェーン継承の他の例を見ることができます。例えば StringDateNumberArrayのグローバルオブジェクトのプロトタイプで定義されたメソッドやプロパティを探してみてください。 これらはすべて、プロトタイプで定義されたメンバーがいくつかあります。これは、たとえば、次のような文字列を作成する場合です。

var myString = 'This is my string.';

myStringには、split()indexOf()replace()などの便利なメソッドが多数用意されています。

Note: このセクションを理解し、もっと詳しく知りたいとき、JavaScriptでプロトタイプを使用するための詳細なガイドを読むことは価値があります。このセクションは、最初に会ったときに理解しやすいように意図的に簡素化されています。

重要: prototypeプロパティは、JavaScriptの最も混乱した部分の1つです。thisは現在のオブジェクトのプロトタイプオブジェクトを指していると思うかもしれませんが、そうではありません (__proto__でアクセスできる内部オブジェクトです。覚えていますか?) 。prototypeは継承するメンバを定義するオブジェクトを含むプロパティです。

create() の再訪

先ほどObject.create()メソッドを使用して新しいオブジェクトインスタンスを作成する方法を示しました。

  1. たとえば、前の例のJavaScriptコンソールでこれを試してください:
    var person2 = Object.create(person1);
  2. create()が実際に行うことは、指定されたプロトタイプオブジェクトから新しいオブジェクトを作成することです。 ここではperson1をプロトタイプオブジェクトとして使用してperson2を作成しています。これを確認するには、コンソールに次のように入力します。
    person2.__proto__

person1を返します。

コンストラクタのプロパティ

すべてのコンストラクタ関数にはprototypeプロパティがあり、その値はconstructorプロパティを含むオブジェクトです。この constructor プロパティは、元のコンストラクタ関数を指し示します。 次のセクションでは、Person.prototype プロパティ(または一般に、コンストラクタ関数のprototypeプロパティで、上のセクションで説明したオブジェクト)で定義されたプロパティが、Person() コンストラクタを使用して作成されたすべてのインスタンスオブジェクトで使用できるようになります。 したがって、constructor プロパティは person1 と person2 の両方のオブジェクトで使用できます。

  1. たとえば、次のコマンドをコンソールで試してみてください。
    person1.constructor
    person2.constructor

    これらのインスタンスには元の定義が含まれているため、両方ともPerson()コンストラクタを返す必要があります。

    巧妙なトリックは、constructorプロパティの末尾にかっこを入れて(必要なパラメータを含む)コンストラクタから別のオブジェクトインスタンスを作成できることです。結局のところ、コンストラクタは関数なので、かっこを使用して呼び出すことができます。この関数をコンストラクタとして使用することを指定するには、newキーワードを含める必要があります。

  2. コンソールでこれを試してみてください:
    var person3 = new person1.constructor('Karen', 'Stephenson', 26, 'female', ['playing drums', 'mountain climbing']);
  3. 次に、新しいオブジェクトの機能にアクセスしてみましょう。
    person3.name.first
    person3.age
    person3.bio()

これはうまくいきます。頻繁に使用する必要はありませんが、新しいインスタンスを作成したい場合や、何らかの理由で簡単に利用できる元のコンストラクターへの参照がない場合は本当に便利です。

constructorプロパティには他の用途もあります。 たとえば、オブジェクトのインスタンスがあり、そのインスタンスであるコンストラクタの名前を返す場合は、次のようにします。

instanceName.constructor.name

たとえば、これを試してみてください:

person1.constructor.name

Note: constructor.nameの値は変更できます (プロトタイプの継承、バインディング、プリプロセッサ、トランスパイラなどのため) 。より複雑な例では、代わりにinstanceof 演算子を使用します。

プロトタイプの変更

コンストラクタ関数のprototypeプロパティを変更する例を見てみましょう(プロトタイプに追加されたメソッドは、コンストラクタから作成されたすべてのオブジェクトインスタンスで使用可能です)。

  1. oojs-class-further-exercises.htmlの例に戻り、ソースコードのローカルコピーを作成します。既存のJavaScriptの下に、コンストラクタのprototypeプロパティに新しいメソッドを追加する次のコードを追加します
    Person.prototype.farewell = function() {
      alert(this.name.first + ' has left the building. Bye for now!');
    };
  2. コードを保存してブラウザにページを読み込み、テキスト入力に次のように入力してみてください:
    person1.farewell();

コンストラクタ内部で定義された人の名前を特徴とする警告メッセージが表示されます。 これは本当に便利ですが、さらに便利なのは、継承チェーン全体が動的に更新され、コンストラクタから派生したすべてのオブジェクトインスタンスでこの新しいメソッドを自動的に利用できることです。

これについて少し考えてみてください。 コードではコンストラクタを定義し、次にコンストラクタからインスタンスオブジェクトを作成し、コンストラクタのプロトタイプに新しいメソッドを追加します。

function Person(first, last, age, gender, interests) {

  // property and method definitions

}

var person1 = new Person('Tammi', 'Smith', 32, 'neutral', ['music', 'skiing', 'kickboxing']);

Person.prototype.farewell = function() {
  alert(this.name.first + ' has left the building. Bye for now!');
};

しかし、person1オブジェクトインスタンスではまだfarewell()メソッドを使用できます。利用可能な機能は自動的に更新されます。

Note: この例がうまくいかない場合は oojs-class-prototype.html の例を見てください (ライブ実行も参照してください) 。

このように定義されているとあまり柔軟ではないので、prototypeプロパティで定義されたプロパティはめったにありません。たとえば、次のようなプロパティを追加できます。

Person.prototype.fullName = 'Bob Smith';

これは人がそう呼ばれないかもしれないので、あまり柔軟ではありません。このように name.firstname.lastからfullNameを構築する方がはるかに良いでしょう:

Person.prototype.fullName = this.name.first + ' ' + this.name.last;

ただしこれは機能しません。thisは、この場合はグローバルスコープであり、関数スコープではありません。このプロパティを呼び出すと、undefined undefinedが返されます。これは、プロトタイプの前半で定義したメソッドでうまく機能しました。これは関数スコープ内にあるため、オブジェクトインスタンススコープに正常に転送されます。したがって、プロトタイプ (つまり、決して変更する必要のないもの) で定数プロパティを定義することもできますが、一般にコンストラクタ内でプロパティを定義する方が効果的です。

実際、より多くのオブジェクト定義のためのかなり一般的なパターンは、コンストラクタ内のプロパティとプロトタイプのメソッドを定義することです。これにより、コンストラクタにはプロパティ定義のみが含まれ、メソッドは別々のブロックに分割されるため、コードを読みやすくなります。例えば:

// Constructor with property definitions

function Test(a, b, c, d) {
  // property definitions
}

// First method definition

Test.prototype.x = function() { ... };

// Second method definition

Test.prototype.y = function() { ... };

// etc.

このパターンは、Piotr Zalewaの学校計画のアプリの例で実際に見られます。

要約

この記事では、プロトタイプオブジェクトのチェーンによってオブジェクトが互いに機能を継承できるようにする方法、プロトタイプのプロパティ、コンストラクタにメソッドを追加する方法、およびその他の関連トピックを含むJavaScriptオブジェクトのプロトタイプについて説明しました。

次の記事では、独自の2つのカスタムオブジェクトの間で機能の継承を実装する方法について説明します。

 

このモジュール内の文書

 

ドキュメントのタグと貢献者

このページの貢献者: silverskyvicto
最終更新者: silverskyvicto,