« prefpane には必ず id を付与する | カスタムツリービューの基本的な使い方(その4~並び替え) » |
setTimeout のコールバック関数内でローカル変数を使用する
var fruits = ["apple", "orange", "banana"];
という配列があるとき、
for (var i = 0; i < fruits.length; i++) { window.setTimeout(function() { alert(fruits[i]); }, i * 1000); }
こうすると1秒おきに「undefined」が3回表示されてしまう。コールバック関数が呼び出されたときにはすでにローカル変数 i は破棄されている i の値が3になっているためである。
以下のようにコールバック関数を文字列にしておけば、1秒おきに「apple」「orange」「banana」が表示される。
for (var i = 0; i < fruits.length; i++) { window.setTimeout("alert('" + fruits[i] + "');", i * 1000); }
あるいは、以下のように setTimeout の第3引数でコールバック関数へ引数を渡す方法もある。コールバック関数の内容が複雑になる場合はこの方が良い。 by Piroさん
for (var i = 0; i < fruits.length; i++) { window.setTimeout(function(aArg) { alert(aArg); }, i * 1000, fruits[i]); }
Firefox 2以降、JavaScript 1.7以降限定 by nanto_viさん
for (var i = 0; i < fruits.length; i++) { window.setTimeout(let (fruit = fruits[i]) function() { alert(fruit); }, i * 1000); }
こちらは Firefox 1.0 でもOK by nanto_viさん
for (var i = 0; i < fruits.length; i++) { with ({ fruit: fruits[i] }) { window.setTimeout(function() { alert(fruit); }, i * 1000); } }
クロージャを使って以下のように書く手もある。 by os0xさん
for (var i = 0; i < fruits.length; i++) { (function(fruit){ window.setTimeout(function() { alert(fruit); }, i * 1000); })(fruits[i]); }
コールバック関数の部分が複雑になる場合は、こっちの方がお勧めかも……
そういう手もありましたか。サンクスです。本文へ加筆しました。
Firefox 2 以降で JavaScript のバージョンを 1.7 以上に指定すれば、
でもいけますね。(Firefox 3 ならバージョンを明示的に指定しなくても使えたかも)
あるいは、
とすれば Firefox 1.0 でも OK ですし。
いずれにせよ、「コールバック関数が呼び出されたときにはすでにローカル変数 i は破棄されている」というのは間違いで、i の値が 4 になっているから fruits[i] が undefined になっているだけです。
便乗させて頂いて、自分ならこう書くかなと。
一つ一つ終わってから処理するときはこうしてます。
nanto_viさんご指摘ありがとうございます。修正しました。
> 一つ一つ終わってから処理する
あまり自信ないですけど、JS1.7でGenerator使うとこんな感じでしょうか。
ジェネレータを使った別解としてこんなのも考えられますね。基本的な発想はhot_coffeeさんと同じです。