クロージャ(closure)についての解説の続き。
前回の記事
【JavaScript】クロージャ(closure) (attacktube.com)
クロージャの挙動について理解を深めていく。
次にように関数オブジェクトadd_the_handlersの内部に関数オブジェクトnodes[i].onclickを定義してクロージャを作った。
これは、liタグの春をクリックすると0、liタグの夏をクリックすると1、liタグの秋をクリックすると2、liタグの冬をクリックすると3とalertするプログラムを作成した。
しかし、春夏秋冬のどれをクリックしても4をアラートしてしまう。これは一体どうゆうことなのか?
<!DOCTYPE HTML>
<html lang="ja">
<head>
<meta charset="utf-8" />
<title></title>
<style type="text/css">
</style>
</head>
<body>
<h3>クロージャとonclick</h3>
<ul id="z01">
<li><a href="#">春</a></li><!-- 4 -->
<li><a href="#">夏</a></li><!-- 4 -->
<li><a href="#">秋</a></li><!-- 4 -->
<li><a href="#">冬</a></li><!-- 4 -->
</ul>
<script type="text/javascript">
window.onload = function() {
var element = document.getElementById("z01").getElementsByTagName("li");
add_the_handlers(element);
}
//間違った方法でイベントハンドラを設定する関数を作成する。
var add_the_handlers = function(nodes) {
var i;
for (i = 0; i < nodes.length; i += 1) {
nodes[i].onclick = function(e) {
alert(i);
};
}
};
</script>
</body>
</html>
JavaScriptの関数は、その関数が作成されたときの環境を保持するように設計されており、関数オブジェクトadd_the_handlersの内部に定義した関数オブジェクトnodes[i].onclickは、外側の関数(add_the_handlers関数)の環境にアクセス可能である。
つまり、nodes[i].onclick関数はadd_the_handlers関数の中で定義した変数iに実際にアクセスしており、コピーした変数iにアクセスしていないので、for文終了後のi=4となったiに実際にアクセスしており、春夏秋冬のどれをクリックしても4をアラートしてしまう。
add_the_handlersの修正
add_the_handlers関数の中で定義したiのコピーをnodes[i].onclick関数に渡すように修正する
nodes[i].onclickに「イベントハンドラ関数を返す即時関数(IIFE)」を設定する。
イベントハンドラ関数は、即時関数に渡されたパラメータであるiの値を処理している。
このiは即時関数の引数であり、add_the_handlersで定義されているiとは異なる変数である。
そして無名関数が返したイベントハンドラ関数が、onclickに設定される。
これにより望みの結果が得られる。
var add_the_handlers = function (nodes) {
var i;
for (i = 0; i < nodes.length; i += 1) {
nodes[i].onclick = (function (i) {
return function (e) {
alert(i);
};
})(i);
}
};
クロージャを理解し工夫すれば望みの結果が得られる。別解1~3も記載した。
別解1
var add_the_handlers = function (nodes) {
var helper = function (i) {
return function (e) {
alert(i);
};
};
var i;
for (i = 0; i < nodes.length; i += 1) {
nodes[i].onclick = helper(i);
}
};
別解2
var add_the_handlers = function (nodes) {
var i;
for (i = 0; i < nodes.length; i += 1) {
(function(){
var j=i;
nodes[i].onclick = function (e) {
alert(j);
};
}());
}
};
別解3
var add_the_handlers = function (nodes) {
var i;
for (i = 0; i < nodes.length; i += 1) {
(function(i){
nodes[i].onclick = function (e) {
alert(i);
};
}(i));
}
};
参考
JavaScript:the good parts 「良いパーツ」によるベストプラクティス [ ダグラス・クロフォード ] ExternalLink P43~46
日経ソフトウェア 2014年10月号(日経BP社) P132~137