CoffeeScriptでliタグにclickイベントを定義していきます。

<ul id="hoge">
  <li>hoge1</li>
  <li>hoge2</li>
  <li>hoge3</li>
</ul>
ul = document.getElementById('hoge')
for li in ul.querySelectorAll('li')
  li.addEventListener('click', (e) ->
    log(li.innerText)
  )

このやり方だと、どこをクリックしてもhoge3と表示されてしまいます。イベントハンドラが発火した時には、forループは既に回りきっているので変数liul.querySelectorAll('li')の一番最後の要素が入っています。

関数の中に関数を作ればクロージャ

まずはループを使わずにクロージャを定義してみます。

outer = ->
  x = 1
  ->
    log("outer: " + x)
    x = x + 1

myFunc = outer()
myFunc()
myFunc()
myFunc()
outer = function() {
  var x;
  x = 1;
  return function() {
    log("outer: " + x);
    return x = x + 1;
  };
};

myFunc = outer();
myFunc();
myFunc();
myFunc();

myFuncには、outer関数の戻り値である無名関数が入ります。なのでmyFuncを実行すると、outer関数の中の無名関数が実行されます。この時、次のようにxは参照されます。

  1. 無名関数内にxが定義されていない。
  2. 外側のレキシカル(関数)スコープでxを探す。
  3. outer関数のxを取得。

outer関数内の変数xは、内側のレキシカルスコープから参照されているのでこのスコープは破棄されず残り続けます。myFuncの実行が終っても、xが再定義されることはありません。維持されます。

JavaScriptは{}を使ったブロックスコープではなく、関数ごとにスコープが作られます。なので関数の中に、関数を作ればクロージャになります。そして内側から外側の変数を参照すれば良いのです。

ループとsetTimeoutでクロージャを使う

for i in [1..3]
  setTimeout(do (i) ->
    ->
      log("setTimeout1: " + i)
  , 200)
for (i = n = 1; n <= 3; i = ++n) {
  setTimeout((function(i) {
    return function() {
      return log("setTimeout1: " + i);
    };
  })(i), 200);
}

CoffeeScriptではdoを使うと無名関数ができます。一緒に引数を指定すると、無名関数の定義場所の同名の変数が引数として渡されます。

  • forループの変数iが、doの仮引数iに渡される。
  • doによる無名関数で新たなレキシカルスコープが作られるので、新しい変数iが定義されることになる。

無名関数に出来た新たな変数iを内側から参照するために、doの中にさらに無名関数を作ります。

liタグにイベントハンドラをセットする

上記のイベントハンドラの書き方を、最初にliタグのclickイベントに使ってみます。

ul = document.getElementById('hoge')
for li in ul.querySelectorAll('li')
  li.addEventListener('click', do (li) ->
    (e) ->
      log(li.innerText)
  )
ul = document.getElementById('hoge');

ref1 = ul.querySelectorAll('li');
for (k = 0, len1 = ref1.length; k < len1; k++) {
  li = ref1[k];
  li.addEventListener('click', (function(li) {
    return function(e) {
      return log(li.innerText);
    };
  })(li));
}

doの無名関数に特に処理は必要ありません。メイン処理の関数を返すだけでいいです。保持したい変数も無名関数の仮引数で作られるので、記述する必要はありません。