オブジェクト指向なJavaScriptプログラミングのススメ(2)

前回 に引き続き、Microsoft のコンサルタント兼トレーナー (であるらしい) で、本も出版している (らしい) Dino さんの記事 Functional vs. Object-Oriented JavaScript Development の後半を要約してみたいと思います。

今回は、jQuery の関数型プログラミング的な側面とオブジェクト指向プログラミング的な側面、そして JavaScript で OO プログラミングをする時の2つの手法 - クロージャーとプロトタイプについてです。

私の理解不足とヘタクソな翻訳を補うため、Dino さんの論点を先にまとめると、次の様になるかと思います。

  • jQuery は モナド のコンセプトを採用し、表に見えるところでは関数型のメリットを最大限生かしている。一方、拡張性を重視し、内部はオブジェクト指向で作られている。
  • JavaScript でオブジェクト指向プログラミングをするには、クロージャー型かプロトタイプ型を用いる。
  • 概念的には、プロトタイプの方が純粋なオブジェクト指向プログラミング言語に近く、またメモリ効率と速度の面でプロトタイプの方が有利である。
  • 関数型とオブジェクト指向のどちらのプログラミング・パラダイムが良いかということではなく、jQuery の成功に見られるように、素直に両方のミックスを受け入れるのが良い。

このエントリーを読んで、直ぐにオブジェクト指向な JavaScript が書けるようになるわけではないですが、関数型とオブジェクト指向型のそれぞれの長所の雰囲気を感じてくれれば幸いです 😉 。

1.プログラミング・パラダイムから見たjQuery

jQuery は関数型とオブジェクト指向の両スタイルをうまくミックスさせたライブラリで、その特徴は次の3つに集約されます。

  • $ は本質的には DOM 要素のラッパーとして働く
  • $(...) のコンストラクタを通して DOM 要素をオブジェクトに渡すことができる
  • $ オブジェクトは、同じ $ オブジェクトを戻り値とする関数に DOM 要素を引き渡しため、関数チェーンを構成することが出来る

これらの特性により、jQuery はモナド なライブラリである、と言うことが出来ます。 (注:モナド とは関数型言語の理論における基本概念とのことらしいです。詳しくは参考情報をご覧ください)。jQuery は、ラッピングされた DOM 要素を操作している限り、関数プログラミングの非常に強力かつクリーンな側面を使うことの出来る、効果的なライブラリと言えます。

一方、関数プログラミングのパラダイムだけが jQuery の魅力ではありません。次のスニペットは jQuery のソースコードの一部ですが、sizeeach を定義した、自分自身のプロトタイプを持っています。

jQuery.fn = jQuery.prototype = {
	init: function (selector, context) {...
	},
	size: function () {
		return this.length;
	},
	each: function (callback, args) {
		return jQuery.each(this, callback, args);
	},
	ready: function (fn) {...
	}:
}

jQuery のプラグインを書くときには、この基本的なプロトタイプに新たな関数を付け加えて拡張していけばよいのです。

この様に、効果的な JavaScript プログラミングとは、常に関数型とオブジェクト指向型をミックスしたものになります。jQuery は、その関数的な側面が見えるところにある、というだけです。

参考情報

2.JavaScriptにおけるオブジェクト

純粋なオブジェクト指向プログラミング言語と JavaScript では、オブジェクトに関して著しい違いがあります。OOP 言語では、クラスがインスタンスとしてのオブジェクトの青写真となります。一方 JavaScript では、オブジェクトの青写真は、オブジェクト自身です。言い換えれば、既存のオブジェクトを継承し、カスタムなオブジェクトを作り、再利用をすることになります。これらはまた、ポリモフィズムの様に振る舞うことが出来ます。

JavaScript を純粋な OOP 言語と同様に使うには、ネイティブな JavaScript オブジェクトの能力を拡張する2つの主要なアプローチから1つを選択しなければなりません - クロージャーとプロトタイプです。

ただしそれらについて言及する前に、JavaScript のネイティブなオブジェクトとその使い方に触れておきます。

JavaScript で新しいオブジェクトを作るには new 演算子を使います。オブジェクト作成後には通常、データと適当な名前を付けた関数のメソッドを配置します。

var person = new Object();
person.Name = "Dino";
person.LastName = "Esposito";
person.BirthDate = new Date(1979, 10, 17)
person.getAge = function () {
	var today = new Date();
	var thisDay = today.getDate();
	var thisMonth = today.getMonth();
	var thisYear = today.getFullYear();
	var age = thisYear - this.BirthDate.getFullYear() - 1;
	if (thisMonth > this.BirthDate.getMonth()) age = age + 1;
	else if (thisMonth == this.BirthDate.getMonth() && thisDay >= this.BirthDate.getDate()) age = age + 1;
	return age;
}

この例では、オブジェクト作成後に person をモデル化しているだけであり、Person オブジェクトを得ているわけではありません。

純粋な OOP 言語と同様なオブジェクトを作る方法は、公開したいメンバーを含む、新しい関数を作ることです。加えて JavaScript の全ての固有なオブジェクトは、read-only な prototype という名の属性を持っており、新しく作られるオブジェクトは、この prototype を通して関数の集合を共有します。

これら2つのメカニズムによって、JavaScript における OOP が効力を発揮するのです。

3.クロージャーによるオブジェクト指向プログラミング

クロージャー は、プログラミング言語における一般的な概念です。JavaScript では、クロージャーは、変数やメソッドを同一のコンテキスト内で定義した関数を指します。この方法では、(匿名あるいは名前付き) 関数がコンテキストを囲みます。次の例は、Person タイプを表す関数を、クロージャーモデルで表したものです。

var Person = function (name, lastname, birthdate) {
	this.Name = name;
	this.LastName = lastname;
	this.BirthDate = birthdate;

	this.getAge = function () {
		var today = new Date();
		var thisDay = today.getDate();
		var thisMonth = today.getMonth();
		var thisYear = today.getFullYear();
		var age = thisYear - this.BirthDate.getFullYear() - 1;
		if (thisMonth > this.BirthDate.getMonth()) age = age + 1;
		else if (thisMonth == this.BirthDate.getMonth() && thisDay >= this.BirthDate.getDate()) age = age + 1;
		return age;
	}
}

見て分かる通り、クロージャーは擬似クラスのコンストラクタに過ぎません。クロージャー・モデルでは、コンストラクタはメンバーの宣言を含み、それらはカプセル化され、クラスに対してプライベートとなります。加えて、それらのメンバーは実体ベースであるため、メモリ使用量が増加します。このオブジェクトを使うには、次の様にします。

var p = new Person("Dino", "Esposito", new Date(...);
alert(p.Name + " is " + p.getAge());

4.プロトタイプによるオブジェクト指向プログラミング

プロトタイプ・モデルでは、prototype オブジェクトを通してクラスに共通な構造を定義します。次のサンプルコードは、クロージャーを避けるため、前述の Person クラスをどのように書き換えるべきかを示した例です。

// Pseudo constructor
var Person = function (name, lastname, birthdate) {
	this.initialize(name, lastname, birthdate);
}
// Members
Person.prototype.initialize(name, lastname, birthdate) {
	this.Name = name;
	this.LastName = lastname;
	this.BirthDate = birthdate;
}
Person.prototype.getAge = function () {
	var today = new Date();
	var thisDay = today.getDate();
	var thisMonth = today.getMonth();
	var thisYear = today.getFullYear();
	var age = thisYear - this.BirthDate.getFullYear() - 1;
	if (thisMonth > this.BirthDate.getMonth()) age = age + 1;
	else if (thisMonth == this.BirthDate.getMonth() && thisDay >= this.BirthDate.getDate()) age = age + 1;
	return age;
}

プロトタイプを用いれば、作ろうとするオブジェクトの prototype に、親オブジェクトのインスタンスを単に設定することで、継承することが出来ます。

var Developer = function (name, lastname, birthdate) {
	this.initialize(name, lastname, birthdate);
}
Developer.prototype = new Person();

内部の関連する関数からプロトタイプのメンバーを参照する場合は、常に this を使う必要があることに注意してください。

参考情報

5.クロージャー VS プロトタイプ

プロトタイプ・モデルでは、全てのインスタンスは継承元のメンバーを共有します。そのため、クロージャーに比べてメモリー使用量は減少しますし、オブジェクトの生成も高速に行われます。構文のユニークさは別として、クラス定義に関しては、クロージャー・モデルよりプロトタイプ・モデルの方が古典的な OOP モデルにかなり近いものになります。

クロージャーかプロトタイプかを選択するとき、性能面やツールのサポートを考慮に入れるべきでしょう。Firefox では、プロトタイプが高速 (IE ではクロージャーの方が高速) ですし、IntelliSense の様なツールベースの補完機能がよく働きます。

またプロトタイプは、リフレクションを使えば型の情報を得ることが出来るため、一々インスタンスを生成する必要がありません。

最後に、プロトタイプはデバッグの際、プライベートなクラスメンバーを見ることが出来ます。クロージャー・モデルを使ったオブジェクトのデバッグは、幾つかの追加的ステップが必要です。

以上の様な理由で、Microsoft の AJAX ライブラリは、プロトタイプ・モデルを採用しています。

6.まとめ

JavaScript は 100% 関数型にもなり、オブジェクト指向型にもなる言語です。どちらのパラダイムにも幅広く適応します。ただし、プログラミング技術に関しては、妥協が必要です。純粋な関数型あるいはオブジェクト指向では許容できないシナリオを、開発者は妥協して受け入れる準備がなければなりません。

JavaScript は今日、Web やモバイルアプリケーション用のクライアント・サイドのコードを書くためには必須です。書くべきクライアント・コードは、もはやこの言語が世の中に紹介された時の DOM スクリプティングだけではなくなりました。JavaScript は、クライアント・サイドのロジックや入力の検証として使われます。開発プロセス全体をより持続可能とするために、jQuery の様な JavaScript ライブラリが使われます。

この jQuery の成功により、JavaScript では関数型プログラミングがオブジェクト指向プログラミングより幅を利かせるでしょう。しかし最終的には、あなたの嗜好やスキル、導入のし易さなどによって、使用するパラダイムを決定すれば良いのです。何も、宗教問題の様に固執する必要はありません。JavaScript を最大限活用するためには、関数型とオブジェクト指向型をミックスして使うことになるでしょう。

もし私が Web フロント・エンド用のクライアント・コードを書くなら、jQuery を採用し、沢山の匿名関数を使い、躊躇なくカスタムなオブジェクトを使うでしょう。反対に、もしサーバー・サイドのフレームワークを記述するならば、オブジェクト指向のアプローチを採用し、クロージャーかプロトタイプによる記述を選択すると思います。

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です

海外からの投稿は受け付けていません