問題
問題のコード
a = [ {}, {} ]
b = [ "a", "b" ]
for s,i in b
a[i].gets = ->(s)
console.log a[0].gets()
console.log a[1].gets()
展開される奴
// Generated by CoffeeScript 1.7.1
var a, b, i, s, _i, _len;
a = [{}, {}];
b = ["a", "b"];
for (i = _i = 0, _len = b.length; _i < _len; i = ++_i) {
s = b[i];
a[i].gets = function() {
return s;
};
}
console.log(a[0].gets());
console.log(a[1].gets());
期待する出力
a
b
現実
b
b
原因
javascriptは変数のスコープを区切る方法がfunctionしかないらしい。そしてcoffeescriptは変数をスコープの一番最初で宣言する。たとえfor-inの一時変数だとしても。ひどいよ。
解決
forの中で一時関数を作って実行すればうまくいくって言ってた。
a = [ {}, {} ]
b = [ "a", "b" ]
for s,i in b
do (s,i) ->
a[i].gets = ->(s)
console.log a[0].gets()
console.log a[1].gets()
感想
なんでforeach死体だけなのに、変な行とインデント挟まにゃならんのだ。coffeescriptはjavascriptをrubyライクに書けるみたいなノリなら(そうなのかどうかは知らないです)、デフォをこっちにして欲しいところです。
ちなみに
JQueryのeachが、「index, value」なのが気に入りません。thisで参照するのは可読性の意味で論外として、eachをするときは基本indexではなくvalueに用事があるのに、いちいちindexを書く手間と変数名を考える手間がひどい。あとArrayにeachが無いのも辛い。
このへんは、最新のブラウザではArray#forEachがあるらしいので、古いブラウザを切り捨てるか互換ライブラリを入れるかそれを使いたいところです。遅いらしいですけど。でもそんなのしたくないからCoffee使ってるのに。
追記
javascriptのクラス拡張がどれくらい怖いか経験少ないのでわからないけど、こうすれば見た目綺麗になれる。
Array.prototype.each = (callback)->
for v,i in this
callback(v,i)
参考
- The Little Book on CoffeeScript - Idioms http://minghai.github.io/library/coffeescript/04_idioms.html
- CoffeeScriptで即時関数 - 四角革命前夜 http://d.hatena.ne.jp/sasaplus1/20110521/1305912309
- Loops don't behave like closures · Issue #3469 · jashkenas/coffeescript https://github.com/jashkenas/coffeescript/issues/3469
- Twitter / selvaggio: @hikaruna まさにこのケースっぽい。 http:// ... https://twitter.com/selvaggio/status/486031011600281600