| « 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]);
}
コールバック関数の部分が複雑になる場合は、こっちの方がお勧めかも……
for (var i = 0; i < fruits.length; i++) { window.setTimeout(function(aArg) { alert(aArg); }, i * 1000, fruits[i]); }そういう手もありましたか。サンクスです。本文へ加筆しました。
Firefox 2 以降で JavaScript のバージョンを 1.7 以上に指定すれば、
for (var i = 0; i < fruits.length; i++) { window.setTimeout(let (fruit = fruits[i]) function() { alert(fruit); }, i * 1000); }でもいけますね。(Firefox 3 ならバージョンを明示的に指定しなくても使えたかも)
あるいは、
for (var i = 0; i < fruits.length; i++) { with ({ fruit: fruits[i] }) { window.setTimeout(function() { alert(fruit); }, i * 1000); } }とすれば Firefox 1.0 でも OK ですし。
いずれにせよ、「コールバック関数が呼び出されたときにはすでにローカル変数 i は破棄されている」というのは間違いで、i の値が 4 になっているから fruits[i] が undefined になっているだけです。
便乗させて頂いて、自分ならこう書くかなと。
var fruits = ["apple", "orange", "banana"]; for (var i = 0; i < fruits.length; i++) { (function(fruit){ window.setTimeout(function() { alert(fruit); }, 1000); })(fruits[i]); }一つ一つ終わってから処理するときはこうしてます。
function setFruitsAlert(fruits) { i = 0; function g() { if(i < fruits.length) { window.setTimeout(function(fruits) { alert(fruits[i]); i++; g(); }, 1000); } } g(); } var fruits = ["apple", "orange", "banana"]; setFruitsAlert(fruits);nanto_viさんご指摘ありがとうございます。修正しました。
> 一つ一つ終わってから処理する
あまり自信ないですけど、JS1.7でGenerator使うとこんな感じでしょうか。
function generator() { var fruits = ["apple", "orange", "banana"]; for (var i = 0; i < fruits.length; i++) { alert(fruits[i]); yield true; } yield false; } function driveGenerator() { if (gen.next()) window.setTimeout(driveGenerator, 1000); else gen.close(); } var gen = generator(); driveGenerator();ジェネレータを使った別解としてこんなのも考えられますね。基本的な発想はhot_coffeeさんと同じです。
function arrayValues(array) { for (var i = 0; i < array.length; i++) yield array[i]; } function forEachLater(array, callback, interval) { var values = arrayValues(array); setTimeout(function () { try { callback(values.next()); setTimeout(arguments.callee, interval); } catch (ex if ex instanceof StopIteration) {} }, interval); } forEachLater(["apple", "orange", "banana"], print, 1000);