これでできる! クロスブラウザJavaScript入門

第14回 プロトタイプと継承

この記事を読むのに必要な時間:およそ 3 分

こんにちは,太田です。前回は総集編的な内容でしたが,今回は一転して基礎編に戻ります。JavaScriptにおける継承の方法とその仕組みについて,今回から数回に分けて基礎的な部分からきっちり押さえていきたいと思います。

JavaScriptとオブジェクト指向

JavaScriptはプロトタイプベースのオブジェクト指向プログラミング言語と言われています。new演算子を用いることで,関数がコンストラクタとして働き,そのコンストラクタが持つプロトタイプオブジェクトのメソッド(プロパティ)を継承した新しいオブジェクトを作ることができます。

なお,オブジェクト指向という概念については今回は触れません。オブジェクト指向という概念を掴みきれていない,自信がないという方は,JavaScriptのprototypeをしっかりと理解してから改めてその概念を学んでみるとすんなりと理解できるかもしれません。さらに,できれば複数の言語で継承などの実装方法を学んでからオブジェクト指向という概念に触れてみると理解を深める助けとなると思います。逆に,いきなり概念から入ることはお勧めできません。

JavaScriptにおけるプロトタイプとnew

まずはごく簡単なプロトタイプとnew演算子のサンプルを見てみましょう。

プロトタイプとnewによるオブジェクトの生成

function Point(x, y){
  this.x = x;
  this.y = y;
}
// var Point = function(){ /*  */ };と書いてもよい
Point.prototype.length = function(){
  return Math.sqrt(this.x * this.x + this.y * this.y);
};
var point1 = new Point(3, 4);
console.log(point1.length()); // 5
console.log(point1);

さらに,次の画像はGoogle Chromeのデベロッパーツール(SafariのWebインスペクタでも同様)のコンソールで上記のコードを実行したアウトプットです(変数point1の中身は展開しています)⁠

図1 Chromeでの実行結果

図1 Chromeでの実行結果

この結果を見ると,point1自身はxとyというプロパティだけ(正確には__proto__という隠しプロパティも持っている)しか持っていないことがわかります。そしてpoint1の__proto__オブジェクトにconstructorプロパティ(function Point)と,lengthメソッドが定義されています。さらにその__proto__オブジェクトにも幾つかのネイティブなメソッドが定義されていることが確認できます。

この__proto__はECMAScriptの仕様では定義されていない,いわゆる独自拡張のプロパティです。元々はFirefoxに由来し,Chrome,Safari,Operaなども__proto__を実装していますが,IEは対応していません。ただ,ECMAScript5ではObject.getPrototypeOfというメソッドが追加され,この__proto__に相当するオブジェクトを取得することが可能になっており,IE 9(プレビュー版)はObject.getPrototypeOfを実装しています。

なお,__proto__自体は非標準ですが,それに相当する概念はECMAScriptに存在しており,__proto__を実装していないブラウザ(主にIE)でも,__proto__に相当する隠しオブジェクトがあると考えて差し支えありません。

さて,上記サンプルコードのnewの辺りの処理をもう少し詳しく解説します。

  • まず関数(Point)を定義し,同時にPointがコンストラクタとなる

  • 関数のprototypeにメソッド(プロパティ)を追加定義し,このメソッドが新しいオブジェクトに継承されるメソッドとなる

  • 関数をnewで呼び出す
    その関数(Point)のprototypeオブジェクトを__proto__オブジェクトとする新しいオブジェクト(point1)が作られ,そのオブジェクト(point1)をthisとして関数Pointが呼び出される

ここで大事なのは,point1.__proto__ === Point.prototype であるという点です。つまり,Point.prototypeに新たなメソッドを追加したり,削除したとき,その影響はpoint1にも及ぶということです。では,Point.prototypeにメソッドを追加してみましょう。

プロトタイプの拡張

Point.prototype.distance = function(point){
  if (!(point instanceof Point)) {
    throw new Error('引数がPointのインスタンスでない');
  }
  var dx = this.x - point.x;
  var dy = this.y - point.y;
  return Math.sqrt(dx * dx + dy * dy);
};
var point2 = new Point(3, -4);
console.log(point1.distance(point2)); // 8
console.log(point1);

図2 Chromeでの実行結果

図2 Chromeでの実行結果

point1がnewで作られた後に,Pointにdistanceというメソッドを追加しました。このdistanceがpoint1からも利用できることが確認できます。

なお上記コードではinstanceofでPointを継承した変数であるか調べています。このようにinstanceofで特定コンストラクタから作られたインスタンスであるか調べることができます。

ただし,こういった制限を設けることはJavaScriptの柔軟さを損なうという見方もあります。例えば,次のようにxとyさえ存在すればよいという方針もあります。

プロトタイプの拡張#2

Point.prototype.distance = function(point){
  var dx = this.x - point.x;
  var dy = this.y - point.y;
  return Math.sqrt(dx * dx + dy * dy);
};
console.log(point1.distance({x:3, y:-4})); // 8

どちらの方針がよいかは一概には言えませんが,複数人・大規模になるほど制限を厳しくすることで全体として動きやすくなることが期待でき,逆に少人数・小規模では制限を設けないことでスピードを生み出すと期待できます。

著者プロフィール

太田昌吾(おおたしょうご,ハンドルネーム:os0x)

1983年生まれ。JavaScriptをメインに,HTML/CSSにFlashなどのクライアントサイドを得意とするウェブエンジニア。2009年12月より、Google Chrome ExtensionsのAPI Expertとして活動を開始。

URLhttp://d.hatena.ne.jp/os0x/

コメント

コメントの記入