15 setTimeout

Posted by CodingWithAlice on April 12, 2021

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)