2 つの問題:
- タイムアウト関数は間違った
i
とのj
値を認識します
- それらはすべて同時に実行されます (200ms 後)
タイムアウト関数は間違ったi
とのj
値を認識します
主な問題は、関数が作成されたときの変数のコピーではなく、変数setTimeout
への永続的な参照が渡されている関数にあることです。これは、すべての関数が、実行時のとの値を参照することを意味します。これは、それぞれ と になります。これは、これらの変数を「閉じる」と呼ばれます (関数は「クロージャー」と呼ばれます)。i
j
i
j
6
50
これを修正する通常の方法は、変更されないものを閉じるように関数を作成することです。それにはいくつかの方法があります。私のお気に入りは、ファクトリ関数を使用することです。
function $(s) { return document.getElementById(s); }
var i = 0;
for (i=0; i<6; i++){
$('t'+i).className = 'show';
cl('t'+i);
for (var j=0; j<50; j++){
cl('b'+i+'='+j+'%');
setTimeout(makeHandler(i, j), 200);
}
}
function makeHandler(ivalue, jvalue) {
return function(){ $('b'+ivalue).style.width = jvalue+'%';};
}
を呼び出すと 、 andmakeHandler
を閉じる関数が返されますが、これは変更されません。または、使い終わったときに maker 関数を破棄できるようにする上記の改良:ivalue
jvalue
function $(s) { return document.getElementById(s); }
var i = 0;
var makeHandler = function(ivalue, jvalue) {
return function(){ $('b'+ivalue).style.width = jvalue+'%';};
};
for (i=0; i<6; i++){
$('t'+i).className = 'show';
cl('t'+i);
for (var j=0; j<50; j++){
cl('b'+i+'='+j+'%');
setTimeout(makeHandler(i, j), 200);
}
}
makeHandler = undefined;
ES5 の機能に依存できる場合 (対象とする環境のため、または ES5 shim を含めたために)、新しい を使用してほぼ同じ効果を得ることができますFunction#bind
。Function#bind
と同じように新しい関数を作成しますがmakeHandler
、エンジンが少し最適化できる可能性は常にあります。
function $(s) { return document.getElementById(s); }
var i = 0;
for (i=0; i<6; i++){
$('t'+i).className = 'show';
cl('t'+i);
for (var j=0; j<50; j++){
cl('b'+i+'='+j+'%');
setTimeout(handler.bind(undefined, i, j), 200);
}
}
function handler(){
$('b'+ivalue).style.width = jvalue+'%';
}
への最初の引数は、関数にあるべきbind
ものです。this
私たちの場合は気にしないので、指定しましたundefined
(これは、関数がブラウザー上でthis
グローバル オブジェクトを参照することを意味します — これが厳密モードコードでない限り、実際には になります)。window
this
undefined
それらはすべて同時に実行されます (200ms 後)
すべての関数は、上記のコードの 200 ミリ秒後に実行されるようにスケジュールされています。そして、彼らはそうするでしょう。:-) 間隔を空けたい場合は、 への呼び出しごとに 200 ミリ秒を増やしますsetTimeout
。i
とを掛けるだけj
です。
setTimeout(makeHandler(i, j), (200 * i * j) + 200);
最初のものは 200 ミリ秒後に実行され、2 番目のものは 200 ミリ秒後に実行されます。全体が完了するまでに約 1 分かかります。これは、6 つすべてが互いに並行して成長するのではなく、最初の要素が成長し、次に次の要素が成長することを前提としています。
または、各関数がその後続関数を呼び出すようにすることもできます。それはおそらく私がすることです。したがって、300 の関数呼び出しをスケジュールするのではなく、1 つの関数呼び出しをスケジュールし、それが発生したときに次をスケジュールします。
function $(s) { return document.getElementById(s); }
// Our counters are here
var i = 0, j = 0;
// This handles the outer portion of the loop (mostly)
function outer() {
$('t'+i).className = 'show';
cl('t'+i);
j = 0;
// Schedule the first inner portion 200ms from now
setTimeout(inner, 200);
}
// This handles the inner portion of the loop (mostly)
function inner() {
// Do this bit
$('b'+i).style.width = j+'%';
++j;
if (j < 50) {
// Continue the inner loop in 200ms
setTimeout(inner, 200);
}
else {
// Set up next outer loop
j = 0;
++i;
if (i < 6) {
// Not done yet, keep going
setTimeout(outer, 200);
}
}
}
// Kick it off
setTimeout(outer, 200);
上記では、次の行も移動しました。
$('t'+i).className = 'show';
cl('t'+i);
...遅延コードに入れました。これは適切だと思いました。
さらに探索する: