Hatena::ブログ(Diary)

放浪するエンジニアの覚え書き このページをアンテナに追加 RSSフィード

2009-07-05

Javascriptの基本:クロージャー(closure)とその読み方

| 05:56 |  Javascriptの基本:クロージャー(closure)とその読み方のブックマークコメント

よく、下のような形式の関数を『クロージャー(closure)』と呼ぶ。

// クロージャー
var test1 = function(){ --- (1)
	var _i = 1;
	return function(){
		alert(_i);
		_i++;
	};
};

形式的には、先のログ『Javascriptの基本:いろいろな関数』で照会した、「関数戻り値にする関数」と同じ。

これを以下のようにして実行すると、'1'、'2’…という値がアラートに上がってくる。

var func = test1(); 

func(); // '1'がアラートされる。
func(); // '2'がアラートされる。
func(); // '3'がアラートされる。

perlruby,phpなどでもクロージャー(と呼ばれるもの)を作ることができる(Java7へのクロージャーの導入は見送られた)。

クロージャーと聞いて「やっかいだな」と思ってしまうのは、これが、「ラムダ関数」であるとか、難しげなものを交えて説明されていること。簡単に言い切ってしまうと、

関数系言語において)「状態を持つ関数」をカプセル化する方法

と見ればよい。

ここでいう「状態」とは、上の関数(1)の変数_iのようなもののこと。javaでいうプロパティーと考えればいい。


このクロージャー、(関数系言語ではなく)オブジェクト指向言語としてJavascriptを捉えた場合、(javaでいうところの)スタティックなオブジェクト、とも見えなくもない。(関数関数を内包しているので、内部クラスを持つ、スタティッククラスといったところか)


ともあれ、このクロージャーを「どう読むか」というのは一つの課題。

先のログ『Javascriptの基本:いろいろな関数』の復習も兼ねて紐解いてみる。

まず、説明のために、上に書いた2つのコードを一つにまとめておく。

// クロージャー
var test1 = function(){
	var _i = 1; // ---(4)
	return function(){
		alert(_i);
		_i++; // ---(5)
	}; // --- (1)
};

var func = test1(); // --- (2)

func(); // '1'がアラートされる。 --- (3)
func(); // '2'がアラートされる。
func(); // '3'がアラートされる。


クロージャーを特徴付けているのは、return文で指定された関数(1)であるが、まず、(2)の代入式が

test1を実行した結果が、funcとラベル付けされる

であることに注意する。ここで、「test1を実行した結果」とは、(4)式の実行とreturn文の実行であるが、return文で指定された無名関数(1)は(当然だが)実行されない。

とすると、このとき何が戻るの? というと、

戻り値は、無名関数(1)への参照

ということになり、それが、funcと名づけられる。

となれば、(3)式は

無名関数(1)の実行

を表すことになる。このとき、

無名関数(1)からは、スコープチェーンによってtest1のローカル変数_iへのアクセスが可能である

から、(5)によって、_iがカウントアップしていく。

「ローカル変数が状態を保持する」という面白い仕組みも、javascriptではスコープチェーンの機能で実現される。この辺りの詳細については、ZDNet:白川俊平氏の記事『JavaScriptクロージャを完全理解!スコープチェインを知る(後編)』に詳しい。

これによれば、ローカル変数のアロケートは関数の呼び出し時に行われる(アクティベーションオブジェクトというらしい)。

したがって、_i=0は(2)式で生成される。(3)式以降はスコープチェーンにより_iにアクセスするため、(_iの初期化は行われず)_iがインクリメントする。

これが、クロージャーの読解。


また、先のログで「即時実行の関数」を照会した。

(function(){
	alert(_i);
	_i++;
})();

この関数を使って、先ほどの例を以下のように書き直してみると面白い。

var test1 = function(){
	// コンストラクタ
	var _i = 1; ---(1) 
	return (function(){
			alert(_i);
			_i++;
			})(); // --- (3)
};
var func = test1; --- (2)

func(); // --- (4)
func();

なんとなく、クロージャー風になった。

ここでの注目は(2)式で、test1の実行結果ではなく、test1の参照をfuncに代入してる。

これを、

var func = test1(); --- (2)'

としてしまうと、test1が実行されたときに、return文で指定された関数(3)も実行されてしまい、1がもどってきてしまう。

そうすると、(4)は無効(パースエラー)になってしまう。

だから、(2)式のようにかくのだが、そのようにしても、(4)式は「test1の実行」となって必ず1を戻す。したがって、func();はずっと1のままとなる(つまり、状態を保持しなくなってしまう)。