4.Web API -> setTimeout

概念

  • 定时器,用来指定某个函数在多少毫秒之后执行。
  • 它会返回一个整数,表示定时器的编号,同时你还可以通过该编号来取消这个定时器。

浏览器典型事件

  • 当接收到 HTML 文档数据,渲染引擎就会将“解析 DOM”事件添加到消息队列中
  • 当用户改变了 Web 页面的窗口大小,渲染引擎就会将“重新布局”的事件添加到消息队列中。
  • 当触发了 JavaScript 引擎垃圾回收机制,渲染引擎会将“垃圾回收”任务添加到消息队列中。
  • 如果要执行一段异步 JavaScript 代码,也是需要将执行任务添加到消息队列中。
  • ...
  • 这些事件被添加到消息队列之后,事件循环系统就会按照消息队列中的顺序来执行事件。

浏览器怎么实现 setTimeout

  • 要执行一段异步任务,需要先将任务添加到消息队列中。
  • 在 Chrome 中除了正常使用的消息队列之外,还有另外一个消息队列,这个队列中维护了需要延迟执行的任务列表,包括了定时器和 Chromium 内部一些需要延迟执行的任务。所以当通过 JavaScript 创建一个定时器时,渲染进程会将该定时器的回调任务添加到延迟队列(hashmap结构)中。
for(;;){ 
  //执行消息队列中的任务 
  Task task = task_queue.takeTask(); 
  ProcessTask(task); 
  //执行延迟队列中的任务 
  ProcessDelayTask()
}
  • 处理完消息队列中的一个任务之后,就开始执行 ProcessDelayTask 函数。ProcessDelayTask 函数会根据发起时间和延迟时间计算出到期的任务,然后依次执行这些到期的任务。等到期的任务执行完成之后,再继续下一个循环过程。通过这样的方式,一个完整的定时器就实现了。

  • clearTimeout

    • JavaScript 引擎会返回一个定时器的 ID。那通常情况下,当一个定时器的任务还没有被执行的时候,也是可以取消的,具体方法是调用 clearTimeout 函数,并传入需要取消的定时器的 ID。
  • 浏览器内部实现取消定时器的操作也是非常简单的,就是直接从 delayed_incoming_queue 延迟队列中,通过 ID 查找到对应的任务,然后再将其从队列中删除掉就可以了。

  • 使用 setTimeout 的一些注意事项

    • 如果当前任务执行时间过久,会影响定时器任务的执行
      • 在使用 setTimeout 的时候,有很多因素会导致回调函数执行比设定的预期值要久,其中一个就是当前任务执行时间过久从而导致定时器设置的任务被延后执行。
      • 通过 setTimeout 设置的回调任务被放入了消息队列中并且等待下一次执行,这里并不是立即执行的;要执行消息队列中的下个任务,需要等待当前的任务执行完成。
    • 如果 setTimeout 存在嵌套调用,那么系统会设置最短时间间隔为 4 毫秒
      • 在 Chrome 中,定时器被嵌套调用 5 次以上,系统会判断该函数方法被阻塞了,如果定时器的调用时间间隔小于 4 毫秒,那么浏览器会将每次调用的时间间隔设置为 4 毫秒。
      • 所以,一些实时性较高的需求就不太适合使用 setTimeout 了,比如你用 setTimeout 来实现 JavaScript 动画就不是一个很好的主意。(函数 requestAnimationFrame 就是个很好的选择。)
    • 未激活的页面,setTimeout 执行最小间隔是 1000 毫秒
      • 如果标签不是当前的激活标签,那么定时器最小的时间间隔是 1000 毫秒,目的是为了优化后台页面的加载损耗以及降低耗电量。
    • 延时执行时间有最大值
      • 如果 setTimeout 设置的延迟值大于 2147483647(32bit 最大只能存放的数字) 毫秒(大约 24.8 天)时就会溢出,那么相当于延时值被设置为 0 了,这导致定时器会被立即执行。
    • 使用 setTimeout 设置的回调函数中的 this 不符合直觉
      • 如果被 setTimeout 推迟执行的回调函数是某个对象的方法,那么该方法中的 this 关键字将指向全局环境,而不是定义时所在的那个对象。
      • 解决方法:
      • 将MyObj.showName放在匿名函数中执行

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

//箭头函数
setTimeout(() => {
    MyObj.showName()
}, 1000);
//或者function函数
setTimeout(function() {
  MyObj.showName();
}, 1000)
  • 使用 bind 方法,将 showName 绑定在 MyObj 上面
  • requestAnimationFrame()
    • 告诉浏览器——你希望执行一个动画,并且要求浏览器在下次重绘之前调用指定的回调函数更新动画。该方法需要传入一个回调函数作为参数,该回调函数会在浏览器下一次重绘之前执行.
    • 若你想在浏览器下次重绘之前继续更新下一帧动画,那么回调函数自身必须再次调用window.requestAnimationFrame()
    • requestAnimationFrame 提供一个原生的API去执行动画的效果,它会在一帧(一般是16ms)间隔内根据选择浏览器情况去执行相关动作。
    • setTimeout是在特定的时间间隔去执行任务,不到时间间隔不会去执行,这样浏览器就没有办法去自动优化。
    • raf的回调函数也是在主线程上执行的,如果其中的一个回调函数执行过久,会影响到其他的任务的.
    • 使用 requestAnimationFrame 不需要设置具体的时间,由系统来决定回调函数的执行时间,requestAnimationFrame 里面的回调函数是在页面刷新之前执行,它跟着屏幕的刷新频率走,保证每个刷新间隔只执行一次,内如果页面未激活的话,requestAnimationFrame 也会停止渲染,这样既可以保证页面的流畅性,又能节省主线程执行函数的开销.

笔记内容来自极客时间李兵老师的《浏览器工作原理与实践》 学习收获了很多 感谢老师

原文地址:https://www.cnblogs.com/liyf-98/p/14416043.html