15 setTimeout - 延迟消息队列
浏览器怎么实现 setTimeout?
渲染进程中所有运行在主线程上的任务都需要 先添加到消息队列 (step1),然后 事件循环系统 (step2)再按照 顺序执行消息队列 中的任务
典型事件:
- 当接收到 HTML 文档数据,渲染引擎就会将 解析 DOM 事件添加到消息队列中
- 当用户改变了 Web 页面的窗口大小,渲染引擎就会将 重新布局 的事件添加到消息队列中
- 当触发了 JavaScript 引擎垃圾回收机制,渲染引擎会将 垃圾回收 任务添加到消息队列中。
- 如果要执行一段异步 JavaScript 代码,也是需要将执行任务添加到消息队列中
特殊事件:
- 为了保证回调函数能在 指定时间内执行,不能将定时器的回调函数直接添加到消息队列中
延迟队列
setTimeout解决方案 : 在 Chrome 中除了正常使用的消息队列之外,还有另外一个消息队列 - 延迟队列,这个队列中维护了需要延迟执行的任务列表,包括了 定时器 和 Chromium 内部 一些需要延迟执行的任务。
延时队列执行时机
处理完消息队列中的一个任务之后,就开始执行延时队列中的函数。
延时队列会根据发起时间和延迟时间计算出到期的任务,然后依次执行这些到期的任务。
等当前的任务执行完成之后,再继续下一个循环过程。
定时器过程中存在的那些陷阱
1、如果当前任务执行时间过久,会影延迟到期定时器任务的执行
2、如果 setTimeout 存在嵌套调用,那么系统会设置最短时间间隔为 4 毫秒
嵌套调用超过 五次
以上,后面每次的调用 最小时间间隔是 4 毫秒。之所以出现这样的情况,是因为在 Chrome 中,定时器被嵌套调用 5 次以上,系统会判断该函数方法被阻塞了,如果定时器的调用时间间隔小于 4 毫秒,那么浏览器会将每次调用的时间间隔设置为 4 毫秒。
3、未激活的页面,setTimeout 执行最小间隔是 1000 毫秒
如果标签不是当前的激活标签,那么定时器最小的时间间隔是 1000 毫秒,目的是为了优化后台页面的加载损耗以及降低耗电量。
4、延时执行时间有最大值 - 超过25天的延时会被立即执行
Chrome、Safari、Firefox 都是以 32 个 bit 来存储延时值的,32bit 最大只能存放的数字是 2147483647 毫秒,这就意味着,如果 setTimeout 设置的 延迟值大于 2147483647 毫秒 (大约 24.8 天)时就会溢出,这导致定时器会被立即执行。
5、使用 setTimeout 设置的回调函数中的 this 不符合直觉
如果被 setTimeout 推迟执行的回调函数是某个对象的方法,那么该方法中的 this 关键字将 指向全局环境,而不是定义时所在的那个对象。
// 代码案例
var name= 1;
var MyObj = {
name: 2,
showName: function(){
console.log(this.name);
}
}
setTimeout(MyObj.showName,1000) // 只把函数内容传入了,在执行的时候,已经没有当前任务的执行上下文
解决方案
// 箭头函数 - this 指向取定义时的执行上下文
setTimeout(() => {
MyObj.showName()
}, 1000);
// 或者 function 函数
setTimeout(function() {
MyObj.showName(); // 函数已被执行,形成闭包
}, 1000)
// 使用 bind 方法 - 改变 this 指向
setTimeout(MyObj.showName.bind(MyObj), 1000)