两个问题:
- 超时函数看到错误
i
和j
值
- 它们都同时运行(200 毫秒后)
超时函数看到错误i
和j
值
主要问题是您传入的函数具有对and变量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+'%';};
}
现在我们调用 makeHandler
, 它返回给我们一个关闭ivalue
and的函数jvalue
,它不会改变。或者对上面的改进,让我们在完成后处理 maker 函数:
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
)。
它们都同时运行(200 毫秒后)
您的所有功能都计划在上述代码后 200 毫秒运行。他们会的。:-) 如果你想把它们分开,你需要为每次调用增加 200ms 到setTimeout
. 我们可以乘以i
and j
:
setTimeout(makeHandler(i, j), (200 * i * j) + 200);
现在第一个将在 200 毫秒后运行,第二个将在 200 毫秒后运行,依此类推。整个过程大约需要一分钟才能完成。这假设您希望第一个元素增长,然后是下一个,然后是下一个,而不是所有六个彼此平行增长。
或者,您可能希望每个函数都调用其后继函数。这大概就是我会做的。因此,与其安排 300 个函数调用,不如安排一个,当它发生时,安排下一个:
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);
...进入延迟的代码,我怀疑这是合适的。
更多探索: