20170418
在参加360面试的时候面试官问到了这样的一道题:
“setInterval和setTimeout有什么区别”
我当时只说出了最基本的区别——一个是倒计时,一个是循环。面试官一脸淫荡的看着我说:“如果setInterval计时器的回调函数执行完需要5秒,而计时器时间间隔为3秒,那会发生什么?”
嘿呀,我居然没想到这一点,但凭借我扎实的JS功底,这点对我来说不是小事。我告诉他那下一次循环就会插入到队列中,等待着此次循环执行完后再执行。
面试官又是一脸淫荡地看着我,“其实并不会,如果之前的代码还没有执行完,那他就会跳过这次循环,再等3秒执行下一次循环。”
将他说的话整理一下应该是下面这样:
0s..............................第一次添加定时器
3s..............................第一次执行定时器代码,并且第二次添加定时器
6s..............................第二次执行定时器代码失败,跳过此次循环,再次添加第二次定时器
8s..............................执行完第一次定时器代码
9s..............................第二次执行定时器代码,并且第三次添加定时器
12s............................第三次执行定时器代码失败,跳过此次循环,再次添加第三次定时器
14s............................执行完第二次定时器代码
“如果之前回调没有执行完,则不能再次执行当前回调,因此会跳过此次回调”
被面试官鄙视了一番,很沮丧。面试官告诉我在《高程3》中写有这部分的内容,让我回去好好看。
回来我就看了一下这部分内容,具体位置在P610的“22.3.1 重复的定时器”,书上是这么说的:
当使用setInterval()时,仅当没有该定时器的任何其他代码示例时,才将定时器代码添加到队列中。这确保了定时器代码加入到队列中的最小时间间隔为指定间隔
今天还和小伙伴们讨论了一番,但我发现我有点误解这段话的意思。
有几个关键词——定时器代码、队列。
其实就是说,当我定时器到了指定时间后,要执行这一波代码时,如果上一波定时器代码还没有完全执行完的话,我就不会执行我这一波代码,也不会将他加入到队列中排队,而是调到下一波倒计时。
看来面试官没蒙我,但是想起他那淫荡的表情我又有些不敢相信,于是我自己做了次试验。
让程序滞留固定时间的方法如下:
function sleep(time) {
let startTime = window.performance.now();
while (window.performance.now() - startTime < time) {}
}
活学活用,window.performance.now()能更精确的获取时间
运行代码如下:
let count = 1;
let getTime = window.performance;
let startTime = getTime.now();
setInterval(function () {
console.log(`第${count}次开始 ${getTime.now() - startTime}`); // 显示开始时间
sleep(500); // 程序滞留500ms
console.log(`第${count}次结束 ${getTime.now() - startTime}`); // 显示结束时间
count += 1;
}, 300); // 300ms间隔
Chrome57中运行结果:

我擦,说好的跳过呢?这结果不就是我当初所认为的那个结果吗?
多试几个浏览器
FireFox47:

Opera:

以及最垃圾的IE:
IE代码要改下:(毕竟IE)
setInterval(function () {
console.log('第' + count + '次开始 ' + (getTime.now() - startTime));
sleep(500);
console.log('第' + count + '次结束 ' + (getTime.now() - startTime));
count += 1;
}, 300);

好嘛,连人家IE都不配合。
难道是浏览器实现时和标准有差别?
三、Node
我又在Node上试了一下,虽然都是V8引擎:
月影老师说:
“定时器任务什么时候放到队列里面是浏览器管理的,而队列里面的任务什么时候执行是引擎处理的”
这里的引擎指的是node或者浏览器内核
“V8是ES引擎,实现的只是ECMA-262部分,timer并不是ECMAScript部分”这里我还要深究一下...

哇塞,良心啊,居然都能保持300ms的间隔
目前得出的结论:
1. 标准中,setInterval()如果前一次代码没有执行完,则会跳过此次代码的执行。
2. 浏览器中,setInterval()如果前一次代码没有执行完,不会跳过此次代码,而是将其插在队列中,等待前一次代码执行完后立即执行。
3. Node中,setInterval()会严格按照间隔时间执行。
具体原因是什么我还在探索中。
网友评论