JS中有两个定时器,setTimeout和setInterval
setTimeout
定义
在指定的延迟时间之后调用一个函数或执行一个代码片段。
语法
let timeID = setTimeout(func,delay)
第一个参数为回调函数,第二个参数为延时的时间,setTimeout方法的返回值是一个数字,为该定时器的ID,这个ID是定时器的唯一标识,用clearTimeout(timeID)可以消除该定时器
setInterval
定义
周期性地调用一个函数(function)或者执行一段代码。
语法和setTimeout一样
关于第二个参数delay
由于javascript 的事件循环机制,导致第二个参数并不代表延迟delay毫秒之后立即执行回调函数,而是尝试将回调函数加入到事件队列。实际上,setTimeout 和 setInterval 在这一点上处理又存在区别:
- setTimeout:延时delay毫秒之后,啥也不管,直接将回调函数加入事件队列。
- setInterval: 延时delay毫秒之后,先看看事件队列中是否存在还没有执行的回调函数(setInterval的回调函数),如果存在,就不要再往事件队列里加入回调函数了。
所以,当我们的代码中存在耗时的任务时,定时器并不会表现的如我们所想的那样。
举一个栗子
function now(){
return new Date()
}
var start = now()
setTimeout(function(){
console.log('第一个setTimeout回调执行等待时间:',now() - start)
var start2 = now()
setTimeout(function(){
console.log('第二个setTimeout回调执行等待时间:',now() - start2)
},100)
}, 100)
得到
但是如果加上耗时的任务,结果就跟我们想的不太一样了
第二个栗子
var timerStart1 = new Date()
setTimeout(function () {
console.log('第一个setTimeout回调执行等待时间:', new Date()- timerStart1);
var timerStart2 = new Date();
setTimeout(function () {
console.log('第二个setTimeout回调执行等待时间:', new Date() - timerStart2);
}, 100)
heavyTask()
}, 100);
var loopStart = new Date()
heavyTask()
console.log('heavyTask耗费时间:', new Date() - loopStart, 'ms')
function heavyTask(){
for(var i = 0; i<1000; i++)
{
console.log(1)
}
}
可以看到在heavytask完成之后,还没有等到100ms,第一个setTimeout就立刻执行了,【即预想中第一个setTimeout的等待时间应该是372+100,而不是373】,同样的第二个setTimeout也没有等heavyTask完成后的100ms再去执行
这就是因为
setTimeout:延时delay毫秒之后,啥也不管,直接将回调函数加入事件队列。
我们来一步步说下事情的经过
1.首先,heavyTask开始执行,它 完成需要372ms.
2.从耗时任务开始执行,第一个定时器等待了100ms后,就想把自己加入事件队列,但是此时前面的耗时任务还没执行完,所以它只能在队列中等待,直到耗时任务执行完毕它才开始执行,所以结果中我们开的看到的是: 第一个setTimeout回调执行等待时间: 373。
3.同理,第二个定时器在等待了100ms之后就急不可耐地想要执行,把自己加入到事件队列中,等到耗时任务执行完毕后,它立马就执行
注意,之前栗子中的第二个定时器是包裹在第一个定时器内部的,
当定时器都是相互独立时,他们加入队列的顺序由delay延迟时间来决定
第三个栗子
setTimeout(function() {
console.log('延迟700ms')
}, 700);
setTimeout(function() {
console.log('延迟100ms')
}, 100);
setTimeout(function() {
console.log('延迟200ms')
}, 200);
var loopStart = new Date()
heavyTask()
console.log('heavyTask耗费时间:', new Date() - loopStart, 'ms')
function heavyTask(){
for(var i = 0; i<5000; i++)
{
console.log(1)
}
}
延迟100ms,延迟200ms,延迟700ms是同时输出的。
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 在准备把回调函数加入到事件队列的时候,会判断队列中是否还有未执行的回调,如果有的话,它就不会再往队列中添加回调函数。 不然,会出现多个回调同时执行的情况。
关于setTimeout(function,0)
我们已经知道setTimeout会等到其他事件执行之后,再执行自己的回调函数,如果第二个参数为0,也需要等待其他所有的事件执行之后才能执行自己的函数
var a = 1;
setTimeout(function(){
a = 2;
console.log(a);
}, 0);
var a ;
console.log(a);
a = 3;
console.log(a);
以上代码会输出1 3 2 setTimeout(function,0)会放在执行队列最后,当其他事件执行之后才开始执行
参考
https://segmentfault.com/a/1190000003764106