クロージャは理解したものだと思っていたけど軽くはまった。
字面通りにJavaScriptで書くと困ったことになる。
function f(n) {
var result = new Array();
for (var i = 0; i < n; i++) {
result.push(function () { return i});
}
return result;
}
var r = f(5);
for (var i = 0; i < r.length; i++) {
r[i](); // => 5
}
これは関数fの中でiへの束縛が1回しか行われていないのが原因。 どんどん新しい束縛を導入するような言語なら起こりにくい。
# Ruby
def f(n)
# ここにi=nilとかやると同じ現象になる
(0...n).inject([]) {|r,i| r << lambda{i} }
end
;; Scheme
(define (f n)
(let loop ((i (- n 1)) (r '()))
(if (< i 0)
r
(loop (- i 1) (cons (lambda () i) r)))))
で、本題はJavaScriptでこれにどう対処するか。問題の箇所にもう一段階functionを被せることで 期待通りに動くようにはなるけど他の手はないんだろうか。
result.push(function (j) {return function () { return j}}(i));
JavaScriptならば起こりやすいとは言ってもこのぐらいなら分かるよなー、と思ってもう一回コードを見てみたら 少し違ってた。
function f(n) {
var result = new Array();
for (var i = 0; i < n; i++) {
var j = i;
result.push(function () { return j});
}
return result;
}
forループの中でさらに var j なんてことをやってた(ちなみにこれだとr[i]()はすべて4になる)。 これなら(Rubyからの切り替えに失敗したと考えれば)はまるのも理解できる。