说到 javascript 中的定时器,咱们确定会想到 setTimeout()
和 setInterval()
这两个函数。本文将从 事件循环(Event Loop) 的角度来分析二者的工做原理和区别。javascript
MDN对 setTimeout
的定义为:java
在指定的延迟时间以后调用一个函数或执行一个代码片断。函数
setTimeout
的语法很是简单,第一个参数为回调函数,第二个参数为延时的时间。函数返回一个数值类型的ID惟一标示符,此ID能够用做 clearTimeout
的参数来取消定时器:oop
var timeoutID = window.setTimeout(code, delay);
IE0+ 还支持回调参数的传入:spa
var timeoutID = window.setTimeout(func, delay, [param1, param2, ...]);
MDN 对 setInterval 的定义为:3d
周期性地调用一个函数(function)或者执行一段代码。日志
因为 setInterval
和 setTimeout
的用法同样,因此这里再也不列出。code
因为javascript 的事件循环机制,致使第二个参数并不表明延迟delay毫秒以后当即执行回调函数,而是尝试将回调函数加入到事件队列。实际上,setTimeout
和 setInterval
在这一点上处理又存在区别:blog
setTimeout:延时delay毫秒以后,啥也无论,直接将回调函数加入事件队列。队列
setInterval: 延时delay毫秒以后,先看看事件队列中是否存在尚未执行的回调函数(setInterval的回调函数),若是存在,就不要再往事件队列里加入回调函数了。
因此,当咱们的代码中存在耗时的任务时,定时器并不会表现的如咱们所想的那样。
下面的代码,原本但愿可以在 100ms
和 200ms
的时候(也就是恰好等待 100ms
)调用回调函数:
var timerStart1 = now(); setTimeout(function () { console.log('第一个setTimeout回调执行等待时间:', now() - timerStart1); var timerStart2 = now(); setTimeout(function () { console.log('第二个setTimeout回调执行等待时间:', now() - timerStart2); }, 100); }, 100); // 输出: // 第一个setTimeout回调执行等待时间: 106 // 第二个setTimeout回调执行等待时间: 107
这样的结果看上去正是咱们所想的那样,可是一旦咱们在代码中加入了耗时的任务时候,结果就不像咱们所指望的那样了:
var timerStart1 = now(); setTimeout(function () { console.log('第一个setTimeout回调执行等待时间:', now() - timerStart1); var timerStart2 = now(); setTimeout(function () { console.log('第二个setTimeout回调执行等待时间:', now() - timerStart2); }, 100); heavyTask(); // 耗时任务 }, 100); var loopStart = now(); heavyTask(); // 耗时任务 console.log('heavyTask耗费时间:', now() - loopStart); function heavyTask() { var s = now(); while(now() - s < 1000) { } } function now () { return new Date(); } // 输出: // heavyTask耗费时间: 1015 // 第一个setTimeout回调执行等待时间: 1018 // 第二个setTimeout回调执行等待时间: 1000
两个 setTimeout
的等待事件因为耗时任务的存在再也不是 100ms
了!咱们来描述一下事情的通过:
首先,第一个耗时任务(heavyTask()
)开始执行,它须要大约 1000ms
才能执行完毕。
从耗时任务开始执行,过了 100ms
, 第一个 setTimeout
的回调函数指望执行,因而被加入到事件队列,可是此时前面的耗时任务还没执行完,因此它只能在队列中等待,直到耗时任务执行完毕它才开始执行,因此结果中咱们开的看到的是: 第一个setTimeout回调执行等待时间: 1018
。
第一个 setTimeout
回调一执行,又开启了第二个 setTimeout
, 这个定时器也是指望延时 100ms
以后可以执行它的回调函数。 可是,在第一个 setTimeout
又存在一个耗时任务,全部它的剧情跟第一个定时器同样,也等待了 1000ms 才开始执行。
能够用下面的图来归纳:
再来看 setInterval
的一个例子:
var intervalStart = now(); setInterval(function () { console.log('interval距定义定时器的时间:', now() - loopStart); }, 100); var loopStart = now(); heavyTask(); console.log('heavyTask耗费时间:', now() - loopStart); function heavyTask() { var s = now(); while(now() - s < 1000) { } } function now () { return new Date(); } // 输出: // heavyTask耗费时间: 1013 // interval距定义定时器的时间: 1016 // interval距定义定时器的时间: 1123 // interval距定义定时器的时间: 1224
上面这段代码,咱们指望每隔 100ms
就打出一条日志。相对于 setTimeout
的区别, setInterval
在准备把回调函数加入到事件队列的时候,会判断队列中是否还有未执行的回调,若是有的话,它就不会再往队列中添加回调函数。 否则,会出现多个回调同时执行的状况。
能够用下面的图来归纳:
上面对javascript定时器执行原理进行了简要的分析,但愿可以帮助咱们更深刻的理解javascript。文中有描述不当的地方能够在评论中指出。