話題の記事

ちょっと高度にJavaScript/クロージャでイベントハンドラを作る

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

前回の解説では、クロージャの「変数が保持される」「変数が隠蔽される」という特徴を使って、アクセサメソッドを作りました。
今回は、「呼ぶたびにクロージャが生まれる」特徴を使ってみます。

繰り返し処理内でイベントハンドラを作る

まずは、何の工夫もなくやってみます。今回はjQueryも使います。

$(function() {
		var data = [
			{label:'リンク1', value:1},
			{label:'リンク2', value:2},
			{label:'リンク3', value:3},
			{label:'リンク4', value:4},
			{label:'リンク5', value:5}
		];
		var ul = $('<ul/>');
		for(var i = 0, l = data.length; i < l; i++) {
			var item = data[i];
			var li = $('<li/>').text(item.label).click(function(e) {
				console.log(e.type, item.value);
			});
			ul.append(li);
		}
		$('body').append(ul);
});

data配列内のオブジェクトからリストを作成し、ulノード内の各liノードに、クリックで各々に対応したvalue値を出力させるイベントハンドラをバインドしたように見えます。お察しの通り、これは正しく動作しません。実際にクリックしてみると、どのノードをクリックしても5が出力されてしまいます。これは、この初期処理を終えた時点の変数itemにdata[4]が代入されているためです。イベントハンドラをバインドした時点でのitemではなく、処理を終えた後のitemを参照してしまっているのです。

では、クロージャをつかって正しく動くように修正してみます。前回、前々回と読んで頂いた方には、もう簡単かもしれませんね。

$(function() {
		var data = [
			{label:'リンク1', value:1},
			{label:'リンク2', value:2},
			{label:'リンク3', value:3},
			{label:'リンク4', value:4},
			{label:'リンク5', value:5}
		];
		var ul = $('<ul/>');
		for(var i = 0, l = data.length; i < l; i++) {
			var item = data[i];
			var li = $('<li/>').text(item.label).click(function () { //エンクロージャ
				var closureItem = item;
				return function(e) { //クロージャ
					console.log(e.type, closureItem.value);
				}
			}());
			ul.append(li);
		}
		$('body').append(ul);
});

繰り返しの度に、イベントハンドラとしてクロージャが生まれます。各々のクロージャ内には、生成された時点でのitemがclosureItemに保存されています。
17行目の()に違和感を感じる方もいるかもしれませんが、これは、定義した匿名関数を直ちに実行する記述です。
また、今回はレキシカル変数としてエンクロージャ内でclosureItemを宣言していますが、このように引数を使う形に書き換えることもできます。

			var li = $('<li/>').text(item.label).click(function (closureItem) { //エンクロージャ
				return function(e) { //クロージャ
					console.log(e.type, closureItem.value);
				}
			}(item));

まとめ

今回の例は、なかなか実用的だったのではないでしょうか。イベントハンドラ以外にも、ajaxの非同期処理などにも応用できます。
注意点として、「呼ぶ度にクロージャが生まれる」ということは、そのぶんメモリをたくさん消費するということでもあります。レキシカル変数にあまり巨大なオブジェクトを取り込むのは、避けたほうが良いでしょう。特にイベントハンドラに使用した場合、対象のノードへの参照が残っている限り、付随するイベントハンドラも残留し続けます。

クロージャについて、3回にわたって解説しました。同じような解説は他にも数多くあるにも関わらず、思いがけず多くの方に読んで頂いたようで、この場を借り、お礼を申し上げます。