前提知识
- JavaScript 是运行于单线程的环境中的,所有任务都在主线程上执行,形成一个执行栈(execution context stack)
- 除了主线程外,还有一个在进程空闲时执行的“代码队列”。随着页面在其生命周期中的推移,代码会按照执行顺序添加入队列。另外,只要异步任务有了结果,回调函数的代码就会被添加到队列中。例如,当某个按钮被按下时,它的事件处理程序代码就会被添加到队列。在 JavaScript 中没有任何代码是立刻执行的,但一旦进程空闲则尽快执行。
- 定时器对“任务队列”的工作方式是,当特定时间过去后将代码插入队列。指定的时间间隔表示何时将定时器的代码添加到队列中,而不是何时实际执行代码。
重复定时器(setIntervel)的缺点
当使用 setIntervel()时,仅当没有该定时器的任何其他代码实例时,才将定时器代码添加到“任务队列”。
这样的机制会导致两个问题:
- 某些间隔会被跳过
- 多个定时器的代码执行之间的间隔可能会比预期的小
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
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。