在for循环内使用setTimeout

18

我想让一个字符串与以下代码逐字符地显示:

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;

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

9个回答

37

试试这样做:

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来在需要时停止它。


你说得对,setInterval 是一个更好的想法,我没有考虑到。 - Jarrett Widman
我明白了,谢谢!在这种情况下,没有定义(标准)循环。所以在这种情况下,函数就像一个循环?这对我来说是新的但很有趣 :) - richard
循环运行的是setInterval。就像你之前所做的那样,它就像在setTimeout上循环 :) - Soufiane Hassou
如果设置innerHTML时出现错误会发生什么?我告诉你:另一个错误,然后是另一个错误,再然后是另一个错误,以此类推。setInterval是危险的。重复使用setTimeout更安全。 - Hemlock
很好,了解了 setInterval 和循环。 - Rahul
如果你不知道延迟时间怎么办?我也有同样的问题,但是在每次循环进入时,我都会为setTimeout(func,delay)设置不同的延迟时间。 - user474470

4

目前,您定义了18个超时,所有超时将同时执行。

第二个问题是,您传递要执行的指令作为字符串。在这种情况下,代码将无法访问initText中定义的所有变量,因为评估后的代码将在全局范围内执行。

我认为,以下代码可以解决这个问题。

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);
        }
    })();
}

3

比@yauhen-yakimovich的答案更加通用:

使用Timeout

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

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毫秒运行一次,但会在执行之间的每个delay之后重复,而loop方法将在每个delay毫秒时触发回调。要提前停止,repeat使用对象作为返回的标识符,因此请使用clearTimeout(timer.t)

用法:

就像@soufiane-hassou的答案中所述:

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);

如前所述,过早终止会是:

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

2

试试这个:

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;

编辑以删除测试变量并将文本[c]作为字符串附加在单引号中。 - Josh Pearce
1
如果文本中包含 '\ 或换行符,则会失败。最好避免在字符串中创建和执行代码,就像 timeout-with-string-argument 一样。 - bobince

1
我想分享一段代码片段(基于Soufiane Hassou的答案)。它适用于当你需要在固定时间间隔内迭代某个数组时。基本上是同步循环,但使用了“sleep”暂停(因为JavaScript不是一种同步编程语言)。
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);
}

使用示例:

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

已在Node JS中进行测试。希望能对某些人有所帮助。

编辑>

以下更新使代码可与执行大量“原型”(如jQuery或prototype)的库一起使用:

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

尝试使用闭包:

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未定义(它是在你的函数内定义的)。

0

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

所以像这样(没有测试过)

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);
    }
}

0

可能更好的方法是级联循环。例如,淡化一个 div:

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);
}

0
如果你想保留 setTimeOut(而不是 setInterval)并使用命名函数(而不是在 setTimeOut 调用中评估代码块),那么这可能会有所帮助。
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;

通过上述方法,您可以将参数传递给追加函数。

要传递多个参数,下面的代码就可以实现:

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()函数中使用作为参数传递的索引引用这些成员。

注意:第二个代码示例并不意味着是最佳或最合适的解决方案,但在其他setTimeOut相关问题中可能会有好处,例如当某人想要进行演示或性能测试时,需要在一定延迟后调用某些功能。这段代码的优点是利用了for循环(许多编码者都想使用for循环)和内部循环的可能性,以及将本地变量“发送”到timeOut函数的循环时间状态的能力。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接