クロージャは理解したものだと思っていたけど軽くはまった。
字面通りに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からの切り替えに失敗したと考えれば)はまるのも理解できる。