継承とプロトタイプチェーン

JavaScript は、 Java や C++ のようなクラスベースの言語を経験した開発者にとってやや紛らわしく、動的で、かつ class の実装が提供されません( class というキーワードが予約語で、変数名に用いることができないにもかかわらず)。

継承に関して言えば、 JavaScript には 1 つだけオブジェクトに関する概念があります。あるオブジェクトはプロトタイプと呼ばれる、他のオブジェクト(または null )への内部的な繋がりを持ちます。このオブジェクトは、あるオブジェクトがそのプロトタイプとして null を持つまで、なおプロトタイプを持ちます。このような、オブジェクトが他のオブジェクトのプロトタイプとなることの連鎖を、プロトタイプチェーンと呼びます。

プロトタイプチェーンと継承

プロパティの継承

JavaScript のオブジェクトはプロパティ(自身のプロパティを指す)の動的な「かばん」で、プロトタイプオブジェクト(または null )への繋がりを持っています。以下に、プロパティにアクセスを試みたときに、何が起こるのかを示します。

// o というオブジェクトとそのプロトタイプチェーンについて、以下のように仮定してみましょう。
// {a:1, b:2} ---> {b:3, c:4} ---> null
// 'a' と 'b' は o 自身のプロパティです。

// この例では、 someObject.[[Prototype]] は someObject のプロトタイプを指定します。
// これは ECMAScript 標準で使われているものに基づく単一表記法で、スクリプトの中で使用することはできません。

console.log(o.a); // 1
// o には、自身のプロパティとして 'a' があるでしょうか?はい、その値は1です。

console.log(o.b); // 2
// o には、自身のプロパティとして 'b' があるでしょうか?はい、その値は2です。
// o のプロトタイプにも 'b' プロパティがありますが、アクセスされません。これを「property shadowing」と呼びます。

console.log(o.c); // 4
// o には、自身のプロパティとして 'c' があるでしょうか?いいえ、そのプロトタイプを確認します。
// o.[[Prototype]] には、自身のプロパティとして 'c' があるでしょうか?はい、その値は4です。

console.log(o.d); // undefined
// o には、自身のプロパティとして 'd' があるでしょうか?いいえ、そのプロトタイプを確認します。
// o.[[Prototype]] には、自身のプロパティとして 'd' があるでしょうか?いいえ、そのプロトタイプを確認します。
// o.[[Prototype]].[[Prototype]] は null であるため探索を中止し、プロパティが見つからなかったため undefined を返します。

あるオブジェクトにプロパティをセットすると、自身のプロパティが作られます。この取得と設定の動作の規則の唯一の例外は、 getter または setter とのプロパティの継承が起こるときです。

「メソッド」の継承

JavaScript それ自体に「メソッド」はありません。 JavaScript には関数があり、これらの関数はプロパティの値として使用することができます。 関数の継承(メソッドのオーバーライドの一種)は、上で見せたような property shadowing を含めたどのような値とも、同じように働きます。

関数がオブジェクトのプロパティ(自身の、または継承された)であることのたった1つの違いは、関数が実行されるときの this の値です。

var o = {
  a: 2,
  m: function(b){
    return this.a + 1;
  }
};

console.log(o.m()); // 3
// この場合に o.m が呼び出されたとき、 'this' は o を指します。

var p = Object.create(o);
// p はオブジェクトで、 p.[[Prototype]] は o です。

p.a = 12; // p に a という自身のプロパティを作ります。
console.log(p.m()); // 13
// p.m が呼び出されるとき、 'this' は p を指します。 'this.a' は p 自身のプロパティです。

オブジェクトの色々な作成方法と、発生するプロトタイプチェーン

構文構造によるオブジェクト生成

var o = {a: 1};

// このオブジェクトは Object.prototype をプロトタイプとして生成されます。
// これは o.hasOwnProperty('a') と呼び出すことを許すものです。
// hasOwnProperty は Object.prototype 自身のプロパティです。
// Object.prototype のプロトタイプは null です。
// o ---> Object.prototype ---> null

var a = ["yo", "whadup", "?"];

// 配列は Array.prototype ( indexOf 、 forEach などのようなメソッドを持っている) から継承します。 
// プロトタイプチェーンは以下のようになります。
// a ---> Array.prototype ---> Object.prototype ---> null

function f() {
  return 2;
}

// 関数は Function.prototype ( call、bind などのようなメソッドを持つ) から継承します。
// f ---> Function.prototype ---> Object.prototype ---> null

コンストラクタ関数を用いる方法

JavaScriptに於ける「コンストラクタ」は、関数を new 演算子を使って呼び出す事で実現可能です。

function Graph() {
  this.vertexes = [];
  this.edges = [];
}

Graph.prototype = {
  addVertex: function(v){
    this.vertexes.push(v);
  }
};

var g = new Graph();
// g は「vertexes」と「edges」の自身のプロパティを持つオブジェクトです。
// g.[[Prototype]] はインスタンス化する時点の Graph.prototype の値です。

【訳注: 他の関数と区別しやすくする目的で、コンストラクタ関数の関数名の最初の一文字を大文字にしておく慣例があります。】

Object.create メソッドを用いる方法

ECMAScript 5 は Object.create という新しいメソッドを紹介しています。このメソッドを呼び出すと、新しいオブジェクトが生成されます。関数の最初の引数が、このオブジェクトのプロトタイプになります。

var a = {a: 1}; 
// a ---> Object.prototype ---> null

var b = Object.create(a);
// b ---> a ---> Object.prototype ---> null
console.log(b.a); // 1 (継承された)

var c = Object.create(b);
// c ---> b ---> a ---> Object.prototype ---> null

var d = Object.create(null);
// d ---> null
console.log(d.hasOwnProperty); // undefined、なぜなら d は Object.prototype から継承していないからです。

Document Tags and Contributors

Contributors to this page: ethertank, sii
最終更新者: ethertank,