【Javascript】クロージャの挙動をonclickメソッドを使って知る

  • 1
    いいね
  • 0
    コメント
001a.html

<!DOCTYPE HTML>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>クロージャとonclick</title>
<style type="text/css">
</style>
</head>
<body>

<h3>クロージャとonclick</h3>

<ul id="z01" >
<li><a href="#">0</a></li><!-- 4 -->
<li><a href="#">1</a></li><!-- 4 -->
<li><a href="#">2</a></li><!-- 4 -->
<li><a href="#">3</a></li><!-- 4 -->
</ul>

<script type="text/javascript">

window.onload=function(){

  var element = document.getElementById("z01").getElementsByTagName("li");

  for(var i=0;i<element.length;i+=1){
    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>

解説

クロージャとは何か?
日経ソフトウェア 2014年10月号P137には「クロージャは環境付き関数オブジェクト」と書いてある。
この「クロージャは環境付き関数オブジェクト」ってのをonclickメソッドを使った上記サンプル001a.htmlで説明する。

001a.htmlはリストにある0をクリックすると0をアラート、1をクリックすると1をアラート、2をクリックすると2をアラート、3をクリックすると3をアラートするようにadd_the_handlersを作成したつもりだが、0から4のどれをクリックしても4をアラートしてしまう。これは一体どうゆうことなのか?

add_the_handlers関数の内部に定義した関数オブジェクトnodes[i].onclickをクロージャと言う。

Javascriptの関数は、その関数が作成されたときの環境を保持するように設計されており、add_the_handlers関数の内部に定義したnodes[i].onclick関数は、外側の関数(add_the_handlers関数)の環境にアクセス可能である。
(※ここでの環境とはローカル変数などを含む変数のかたまりことを言う)
つまり、nodes[i].onclick関数はadd_the_handlers関数の中で定義した変数iに実際にアクセスしており、コピーした変数iにアクセスしていないので、for文終了後のi=4となったiに実際にアクセスしており、001a.htmlのリストにある1~3のどれをクリックしても4をアラートしてしまう。このように、内部関数が外側関数が作成されたときの環境にアクセス可能なことから、クロージャは環境付き関数オブジェクトと呼ばれる。

add_the_handlersを修正する

修正方法:add_the_handlers関数の中で定義したiのコピーをnodes[i].onclick関数に渡すように修正する。

nodes[i].onclickに即時関数(IIFE)を代入し、この即時関数にadd_the_handlers関数の中で定義したiを渡し、イベントハンドラ関数を返す仕様にすると、即時関数に渡したiはコピーされることになり、即時関数のローカル変数iとなる。これによって、外部関数が即時関数、内部関数がイベントハンドラ関数となり、イベントハンドラ関数は即時関数のローカル変数i(add_the_handlers関数の中で定義したiのコピー)にアクセルすることにより、望みの結果が得られる。

クロージャを理解し工夫すれば望みの結果が得られる。別解1~3も記載した。

修正版
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

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に紹介されているadd_the_handlers関数である。
add_the_handlers関数の引数がnodesってあるが、このnodesって何を渡すのかイマイチ理解できていない。
nodesにdocument.getElementById("").getElementsByTagName("");を渡すと動いた。
また、日経ソフトウェア2014年10月号の記事も参考にこの記事を書いた。
Javascript The Good Partsではクロージャの説明に「関数はそれが作成された際のコンテキストへのアクセスが可能になっている」と書いてあったが、この「コンテキスト」は「外側の関数が作成されたときの環境(ローカル変数などを含む変数のかたまり)」のことを言ってると思われる。

参考文献

Douglas Crockford 著 水野貴明 訳『JavaScript:The Good Parts』(オライリー・ジャパン発行、2008) P43~46
日経ソフトウェア 2014年10月号(日経BP社) P132~137