CoffeeScriptで複数のliタグにclickイベントをセットする
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ループは既に回りきっているので変数liにul.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は参照されます。
- 無名関数内に
xが定義されていない。 - 外側のレキシカル(関数)スコープで
xを探す。 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の無名関数に特に処理は必要ありません。メイン処理の関数を返すだけでいいです。保持したい変数も無名関数の仮引数で作られるので、記述する必要はありません。