14-setTimeout如何实现

99 阅读2分钟

SetTimeout如何实现

浏览器如何实现setTimeout

在上一节中,我们的事件循环系统是通过消息队列的顺序来执行的

将任务放到消息队列之后,事件循环系统就会按照消息队列中的顺序来执行任务

但是定时器设置回调函数有点特别,他们需要在指定时间间隔内被调用,但是消息队列中的任务是顺序调用执行的,所以为了回调函数能在指定时间内执行,我们不能将定时器的回调函数添加到消息队列中

所以,在Chrome中除了正常使用的消息队列之外,还有另一个消息队列,这个队列中维护了需要延迟执行的任务列表,包括了定时器和Chromium内部一些需要延迟执行的任务

延迟队列的执行时机:处理完消息队列中的一个任务之后,就去执行延迟队列中,根据发起时间和延迟时间计算出到期的任务,然后依次执行到期的任务,等到期的任务执行完成,再继续下一个循环过程取消的话,JavaScript 引擎直接从延迟队列中通过定时器的 ID 查找到对应的任务,然后将其删除

使用setTimeout的注意事项

  • 如果当任务执行时间过久,会影响延迟到期定时器任务的执行

    很多因素会导致回调函数的执行要比设定的预期值要晚,其中一个就是当前任务执行时间过久从而导致定时器设置的任务被延后执行

     function bar() {
         console.log('test')
     }
     function foo() {
         setTimeout(bar, 0)
         for (let i = 0; i < 5000; i++) {
             let i = 5 + 5 + 8 + 8;
             console.log(i)
         }
     }
     foo()
    

    在这段代码中,bar的执行时间明显要晚于0ms

  • 如果setTimeout存在嵌套调用,那么系统会设置最短时间间隔4ms

    在定时器函数中嵌套调用定时器,也会延长定时器的执行时间,如:

      function cb(){
          setTimeout(cb, 0)
      }
     setTimeout(cb, 0)
    

    前五次的时间调用间隔比较小,嵌套超过5次以上,后面每次调用最小时间间隔是4ms

    因为Chrome中,定时器被嵌套调用5次以上,系统会判断该函数方法被阻塞了,如果定时器的调用时间间隔小于4ms,那么就会将每次调用的时间间隔设置为4ms

  • 未激活的页面,setTimeout执行最小间隔是1000ms

    除了 4 ms 延迟,未被激活得页面定时器最小值大于 1000 ms

    目的是为了优化后台页面的加载损耗以及降低耗电量

  • 延迟执行时间有最大值

    Chrome 以32个 bit 来储存延时值,231 ms(约为24.8天,2147483647ms),超过就会立即执行

      function bar() {
         console.log('test')
     }
     setTimeout(bar, 2147483648) //立即执行
    
  • 使用setTimeout设置的回调函数中this不符合直觉

     var name = 1
     var myObj = {
         name: 2,
         showName: function () {
             console.log(this.name)
         }
     }
     setTimeout(myObj.showName, 1000)
    

    这段代码在编译时,执行上下文中的this会被设置为全局window,严格模式下设置为undefiend

    可以使用箭头函数或者放在匿名函数中执行

     setTimeout(() => {
         MyObj.showName()
     }, 1000)
     setTimeout(function(){
         MyObj.showName()
     }, 1000)
    

    也可以使用**bind方法**,将showName绑定在MyObj上:

     setTimeout(MyObj.showName.bind(MyObj), 1000)