前提知识

  • JavaScript 是运行于单线程的环境中的,所有任务都在主线程上执行,形成一个执行栈(execution context stack)
  • 除了主线程外,还有一个在进程空闲时执行的“代码队列”。随着页面在其生命周期中的推移,代码会按照执行顺序添加入队列。另外,只要异步任务有了结果,回调函数的代码就会被添加到队列中。例如,当某个按钮被按下时,它的事件处理程序代码就会被添加到队列。在 JavaScript 中没有任何代码是立刻执行的,但一旦进程空闲则尽快执行。
  • 定时器对“任务队列”的工作方式是,当特定时间过去后将代码插入队列。指定的时间间隔表示何时将定时器的代码添加到队列中,而不是何时实际执行代码。

重复定时器(setIntervel)的缺点

当使用 setIntervel()时,仅当没有该定时器的任何其他代码实例时,才将定时器代码添加到“任务队列”。
这样的机制会导致两个问题:

  1. 某些间隔会被跳过
  2. 多个定时器的代码执行之间的间隔可能会比预期的小

Eg:假设某个 onclick 时间处理程序使用 setIntervel()设置了一个200ms间隔的重复定时器。如果事件处理程序需要花 300ms 的时间完成,定时器代码也花了差不多的时间,例子代码如下:

btn.onclick = function() {
    setIntervel(function() {
        //...
        //定时器代码,耗时300ms多
    }, 200);  //每隔 200ms 将定时器代码插入队列
        
    //...
    //事件处理程序,耗时300ms
}

重复定时器的进程时间线

  • 605ms处,第一个定时器代码(在5ms处添加的)仍在运行,同时队列中已经有了一个定时器代码实例(在405ms时添加的),则605ms处本应被添加到队列的定时器代码不会被添加到队列中。
  • 在600ms多一点处,第一个定时器代码(在5ms处添加的)结束之后,队列中的定时器代码(在405ms处添加的)就立即执行。而代码的本意是隔200ms再执行下一个定时器代码。

链式调用setTimeout()

setTimeout(function() {
    //处理中
    setTimeout(arguments.callee, interval)
}, interval)

这个模式的好处是:

  • 在前一个定时器代码执行完之前,不会把新的定时器代码添加到代码队列,确保不会跳过任何间隔。
  • 保证了在下一次定时器代码执行之前,至少要等待指定的间隔,避免定时器代码的连续运行。

Eg: 实现将一个 div 向右移动到 left 坐标为 200px 的动画

setTimeout(function() {
    var div = document.getElementById('myDiv');
    var left = parseInt(div.style.left) + 5;
    div.style.left = left + 'px';
    
    if (left < 200) {
        setTimeout(arguments.callee, 50);
    }
    
}, 50)

参考资料

  • 《JavaScript高级程序设计》作者: Nicholas C. Zakas

一丁目
38 声望1 粉丝

live in the moment