关于javascript:setTimeout里面的for循环

setTimeout inside for loop

本问题已经有最佳答案,请猛点这里访问。

我想要一个字符串出现字符转换为以下代码:

1
2
3
4
5
6
7
8
9
10
11
12
function initText()
{
    var textScroller = document.getElementById('textScroller');
    var text = 'Hello how are you?';

    for(c = 0; c < text.length; c++)
    {
        setTimeout('textScroller.innerHTML += text[c]', 1000);
    }
}

window.onload = initText;

它不起作用..我做错了什么?


尝试这样的事情:

1
2
3
4
5
6
7
8
9
10
11
12
13
function initText()
{
    var textScroller = document.getElementById('textScroller');
    var text = 'Hello how are you?';

    var c = 0;
    var interval = setInterval(function() {
                          textScroller.innerHTML += text[c];
                          c++;
                          if(c >= text.length) clearInterval(interval);
                   }, 1000);

}

注意我添加clearInterval以在需要时停止它。


目前,您正在定义18个超时,所有将立即执行。
第二个问题是,您将指令作为String传递。在这种情况下,代码将无法访问initText中定义的所有变量,因为已评估的代码将在全局范围内执行。

IMO,这应该做的工作

1
2
3
4
5
6
7
8
9
10
11
12
13
function initText(){
    var textScroller = document.getElementById('textScroller');
    var text = 'Hello how are you?';

    var c = 0;

    (function(){
        textScroller.innerHTML += text.charAt(c++);
        if(text.length > c){
            setTimeout(arguments.callee, 1000);
        }
    })();
}


甚至比@ yauhen-yakimovich回答更通用:

使用Timeout

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var repeat = (function () {
    return function repeat(cbWhileNotTrue, period) {
        /// <summary>Continuously repeats callback after a period has passed, until the callback triggers a stop by returning true.  Note each repetition only fires after the callback has completed.  Identifier returned is an object, prematurely stop like `timer = repeat(...); clearTimeout(timer.t);`</summary>

        var timer = {}, fn = function () {
            if (true === cbWhileNotTrue()) {
                return clearTimeout(timer.t); // no more repeat
            }
            timer.t = setTimeout(fn, period || 1000);
        };
        fn(); // engage
        return timer; // and expose stopper object
    };
})();

使用Interval

1
2
3
4
5
6
7
8
9
10
var loop = (function () {
    return function loop(cbWhileNotTrue, period) {
        /// <summary>Continuously performs a callback once every period, until the callback triggers a stop by returning true.  Note that regardless of how long the callback takes, it will be triggered once per period.</summary>

        var timer = setInterval(function () {
            if (true === cbWhileNotTrue()) clearInterval(timer);
        }, period || 1000);
        return timer; // expose stopper
    };
})();

注释中指示的两者之间略有差异 - repeat方法仅在回调执行后重复,因此如果您有"慢"回调,它将不会每delay ms运行,而是在每次delay之后重复执行,而loop方法将每delay ms触发一次回调。要提前停止,repeat使用对象作为返回的标识符,因此请改用clearTimeout(timer.t)

用法:

就像@ soufiane-hassou的回答一样:

1
2
3
4
5
6
7
8
9
var textScroller = document.getElementById('textScroller');
var text = 'Hello how are you?';

var c = 0;
var interval = repeat/* or loop */(function() {
                      textScroller.innerHTML += text[c];
                      c++;
                      return (c >= text.length);
               }, 1000);

如上所述,过早停止将是:

1
2
/* if repeat */ clearTimeout(interval.t);
/* if loop */   clearInterval(interval);

试试这个:

1
2
3
4
5
6
7
8
9
10
11
12
function initText()
{
    var textScroller = document.getElementById('textScroller');
    var text = 'Hello how are you?';

for(c = 0; c < text.length; c++)
{
    setTimeout("textScroller.innerHTML += '" + text[c] +"'", 1000 + c*200);
}
}

window.onload = initText;


我想分享一个片段(根据Soufiane Hassou的回答)。它延伸到这样一种情况,即你在一个固定的时间间隔内替换一个for循环体来迭代某个数组。基本相同的同步循环,但"睡眠"暂停(因为javascript不是同步编程语言)。

1
2
3
4
5
6
7
8
function loop(arr, take, period) {
    period = period || 1000;
    var i = 0;
    var interval = setInterval(function() {
        take(i, arr[i]);
        if (++i >= arr.length) { clearInterval(interval);}
    }, period);
}

用法示例:

1
2
3
loop([1, 2, 3, 4], function(index, elem){
    console.log('arr[' + index + ']: ' + elem);
});

在Node JS中测试过。希望能帮助别人。

编辑>

以下更新使代码可以与libs一起使用繁重的"原型设计"(如jQuery或原型):

1
2
3
4
5
6
7
8
9
10
11
12
13
function loop(arr, take, period) {
    period = period || 1000;
    var scope = {
        i: 0,
        arr: arr,
        take: take,
    };
    var iterate = (function iterate() {
        if (this.i >= this.arr.length) { clearInterval(this.interval); return}
        take(this.i, this.arr[this.i++]);
    }).bind(scope);
    scope.interval = setInterval(iterate, period);
}

尝试使用闭包:

1
2
3
4
5
6
7
8
9
10
11
12
function init() {
    var textScroller = document.getElementById('textScroller');
    var text = 'Hello how are you?';
    var c = 0;
    function run() {
        textScroller.innerHTML += text[c++];
        if (c<text.length)
            setTimeout(run, 1000);
    }
    setTimeout(run, 1000);
}
init()

您的代码中的问题是您放入字符串的代码将在全局上下文中运行,其中未定义textScroller(它在您的函数内定义)。


可能更好地循环级联。例如淡化div:

1
2
3
4
5
6
7
8
div=document.createElement('div');
div.style.opacity=1;
setTimeout(function(){fade(1);},3000);
function fade(op){
    op-=.05;
    if(op>0) setTimeout(function(){div.style.opacity=op;fade(op);},30);
    else document.body.removeChild(div);
}

如果你想保留setTimeOut(而不是setInterval)并使用命名函数(而不是在setTimeOut调用中评估代码块),那么这可能会有所帮助:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
var b = {
  textScroller: document.getElementById('textScroller'),
  text:"Hello how are you?"
};


function initText() {
  for(c = 0; c < b.text.length; c++) {
    setTimeout("append("+c+")", 1000 + c*200);
  }
}

function append(c) {
  b.textScroller.innerHTML += b.text[c];
}

window.onload = initText;

通过上面的内容,您可以将参数传递给追加函数。

要传递几个参数,下一个代码可以解决问题:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
var glo = [];

function initText()
{
  var textScroller = document.getElementById('textScroller');
  var text ="Hello how are you?";
  var timeout_time;
  for(c = 0; c < text.length; c++) {
    glo[glo.length] = {text:text, c:c, textScroller:textScroller};
    timeout_time = 1000 + c * 200;
    setTimeout("append(" + (glo.length - 1) +")", timeout_time);
  }
}

function append(i)
{
  var obj = glo[i];
  obj.textScroller.innerHTML += obj.text[obj.c];
  obj = null;
  glo[i] = null;
}

window.onload = initText;

使用上面的内容,您只有一个全局数组glo。在循环中,您将新的数组成员创建为glo,而在append()函数中,使用作为参数传递的索引来引用这些成员。

注意:第二个代码示例并不是OP问题的最佳或最合适的解决方案,但可能会受益于其他setTimeOut相对问题,例如。当有人想要进行演示或性能测试时,需要在延迟后调用某些功能。这段代码的优点是可以使用for循环(许多编码器想要用于循环)以及使用内循环的可能性以及将其循环时间状态中的局部变量"发送"到timeOut函数的能力。


你的for循环是一次为每个字符设置一个超时,所以它们不会按顺序出现,而是一次性出现。你的setTimeout应该包含另一个setTimeout的代码,该setTimeout将包含要显示的下一个字符。

所以这样的事情(没有测试这个)

1
2
3
4
5
6
7
8
9
10
11
12
function initText()
{
    var textScroller = document.getElementById('textScroller');
    var text = 'Hello how are you?';    
    setTimeout('nextChar(text)', 1000);
}
function nextChar(text){
    if(text.length > 0){
        textScroller.innerHTML += text[0];
        setTimeout('nextChar(text.substring(1))', 1000);
    }
}