浏览器如何实现setTimeout
执行一段异步任务,需要先将任务添加到消息队列中。不过通过定时器设置回调函数有点特别,它们需要在指定的时间间隔内被调用,但消息队列中的任务是按照顺序执行的,所以为了保证回调函数能在指定时间内执行,你不能将定时器的回调函数直接添加到消息队列中。
那应该怎么设计才能让定时器设置的回调事件在规定时间内被执行呢?
在Chrome中除了正常使用的消息队列之外,还有另外一个消息队列,在这个队列中维护了需要延迟执行的任务列表,包括了定时器和Chromium内部一些需要延迟执行的任务。所以当通过JavaScript创建一个定时器时,渲染进程会将该定时器的回调任务添加到延迟队列中。
使用setTimeout的一些注意事项
1、当前任务执行时间过久,会影响定时器任务的执行
在使用 setTimeout 的时候,有很多因素会导致回调函数执行比设定的预期值要久,其中一个就是当前任务执行时间过久从而导致定时器设置的任务被延后执行。我们先看下面这段代码:
function bar() {
console.log('bar')
}
function foo() {
setTimeout(bar, 0);
for (let i = 0; i < 5000; i++) {
let i = 5+8+8+8
console.log(i)
}
}
foo()
bar函数是在下面这个for循环执行完才会执行,虽然设置的delay是0,但for循环了5000次是个耗时操作,所以当前这个任务的执行时间会比较久一点。这势必会影响到下个任务的执行时间。
2、未激活的页面,setTimeout执行最小间隔是1000ms
未被激活的页面中定时器最小值大于 1000 毫秒,也就是说,如果标签不是当前的激活标签,那么定时器最小的时间间隔是 1000 毫秒,目的是为了优化后台页面的加载损耗以及降低耗电量。这一点你在使用定时器的时候要注意。
3、延时执行时间有最大值
除了要了解定时器的回调函数时间比实际设定值要延后之外,还有一点需要注意下,那就是 Chrome、Safari、Firefox 都是以 32 个 bit 来存储延时值的,32bit 最大只能存放的数字是 2147483647 毫秒,这就意味着,如果 setTimeout 设置的延迟值大于 2147483647 毫秒(大约 24.8 天)时就会溢出,那么相当于延时值被设置为 0 了,这导致定时器会被立即执行。
4、使用setTimeout设置的回调函数中的this不符合直觉
var name= 1;
var MyObj = {
name: 2,
showName: function(){
console.log(this.name);
}
}
setTimeout(MyObj.showName,1000)
这里输出的是1,因为这段代码在编译的时候,执行上下文中的this会被设置为全局window,如果是严格模式,会被设置成undefined
解决方案有两种:
第一种是采用箭头函数:
setTimeout(() => {
MyObj.showName()
})
第二种是使用bind方法
setTimeout(MyObj.showName.bind(MyObj), 1000)