JS高级定时器

4次阅读

共计 1237 个字符,预计需要花费 4 分钟才能阅读完成。

前提知识

  • 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
正文完
 0