Classオブジェクト

前回に引き続き、prototype.jsのソースコードを読んでいこう。前回の記事掲載後、「Firebug 1.05」、「prototype.js 1.5.1」が正式リリースされている。前回の時点ではprototype.js 1.5.1_rc2を準備したが、せっかくなので正式版で読み進めることにしよう。なお、FirebugについてはライセンスがThe BSD Licenseへ変更されている。詳細については別掲の記事も参考にされたい。

さて、今回は34行目Classオブジェクトの定義から見ていこう。(行頭の数字は行番号を示す。)

34 var Class = {
35   create: function() {
36     return function() {
37       this.initialize.apply(this, arguments);
38     }
39   }
40 }

これまでと変わらないオブジェクト表記方法だが、少し理解し難いかもしれない。結論から言うと、このオブジェクトの役割は、その名の通りクラス定義だ。

連載第3回、第4回の記事では、

  • JavaScriptではクラスという概念が存在しないこと
  • オブジェクトを関数オブジェクトにより定義することで、これを初期化処理として利用できること

を紹介したが、今回登場したClassオブジェクトは、我々にクラス(として利用できるオブジェクト)の定義方法を提供してくれるものだ。まずは、以下のコマンドをFirebugで実行し、Classオブジェクトの利用方法を確認してみよう(今回からは、少し複雑なコードを実行していくため、サンプルコードに改行を含めてある。Firebugでは改行入力ごとに処理が実行されてしまうため、改行無しで入力するか、コンソール [Options]-[Larger Command Line]で複数行入力エリアを用意して実行してほしい。なお、prototype.js読み込み時と同様、下記の複数行のコマンドをプロンプトにコピー&ペーストすれば、自動的に複数行エリアに切り替わるはずだ)。

>>> var Mammal = Class.create()
>>> Mammal.prototype = {
  initialize: function(name) {
    this.name = name;
  },
  bark: function() {
    alert('hogehoge');
  }
}
(実行結果) Object
>>> var m = new Mammal('哺乳類A')
>>> m.name
(実行結果) "哺乳類A"
>>> m.bark()

上記のコードの動きを、prototype.jsと入力したコードの双方を見ながら説明していこう。

prototype.jsを利用する際には、上記のように、Class.create()によりクラスを定義することができる。より丁寧に言えば、var Mammal = Class.create()という表記は、"Mammal変数に(initialize処理を行う)関数オブジェクト(create())を割り当てる"という操作を表している。

クラスを定義すると言っても、(JavaScriptには元々クラスという概念は無いので、)実際は、このように関数オブジェクトを割り当てるということと同じであることを確認しておこう。Classというオブジェクト名を借りることで、"Mammal"という識別子がクラスであることを明示している、とも言える。

new演算子を適用した際、変数に割り当てられた関数オブジェクトが初期化処理として実行されるということは、前回までに確認してきたとおりだ。したがって、Mammal.prototypeで定義したinitialize処理が、new Mammal('哺乳類A')で実行され、この結果得られたオブジェクトがmに代入される、という流れになる。

Class.create()による初期化処理については上記の通りだが、prototypeに定義したbarkプロパティがMammalクラスのメンバメソッドとして定義できていることも確認しておこう。このようなクラス定義方法は、prototype.jsを使用する上での基本構文とも言えるため、しっかりと理解しておきたい。

以上、Classオブジェクトでのクラス定義と処理の流れについて紹介したが、prototype.js 37行目のthis.initialize.apply(this, arguments);について、もう少し補足しておく。applyメソッドはJavaScriptのFunctionオブジェクトで定義されているもので、メソッド実行時のthis参照を指定できるという特徴を持っている。したがって、initialize内部でthis参照が必要ないのであれば、ひとまず、

36     return function() {
37       this.initialize(arguments);
38     }

と、"読みかえて"差し支えない。ただし、JavaScriptプログラミングでは、this参照を解決する/したいシーンが頻発するため、今回登場したFunction.apply()や類似のFunction.call()の動きは確認しておいた方が良いだろう。これらは、今後機会があれば紹介していくこととする。

Objectオブジェクト

続いて、44行目以降のObjectオブジェクトを見ていこう。ObjectオブジェクトはJavaScriptで最初から定義されている最上位のオブジェクトだ。したがって、ここ(prototype.js上)での定義は、Objectオブジェクトの拡張ということになる。

44 Object.extend = function(destination, source) {
45   for (var property in source) {
46     destination[property] = source[property];
47   }
48   return destination;
49 }

ここで定義されているのは、継承を実現するためのextendプロパティだ。次のように実行することで、DogクラスにMammalクラスのプロパティを継承させることができる。

>>> var Dog = Class.create()
>>> Object.extend(Dog.prototype, Mammal.prototype)
function()
>>> var d = new Dog('犬B')
>>> d.name
"犬B"
>>> d.bark()

ここ(44-49行目)での処理は、sourceオブジェクトの各プロパティをdestinationオブジェクトへコピーしているというものだ。継承の記法に加えて、JavaScriptのfor..in文についても確認しておこう。

for..in文では、上記のように、inで指定されたオブジェクトの全プロパティに対する反復処理が実行される。詳しくはCore JavaScript 1.5 Guideなどを参考にしてほしい。

次回も引き続き、prototype.jsを読み進めていこう。