この記事は公開されてから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回にわたって解説しました。同じような解説は他にも数多くあるにも関わらず、思いがけず多くの方に読んで頂いたようで、この場を借り、お礼を申し上げます。