美文网首页Web前端
聊一聊浏览器事件循环与前端性能

聊一聊浏览器事件循环与前端性能

作者: 欣然_d10f | 来源:发表于2018-09-09 16:19 被阅读24次

在网上也看了不少关于javascript事件循环的文章,多数是以浏览器事件循环与nodejs中事件循环做对比,分析两种环境的差异。下面说的内容是浏览器事件循环与前端性能之间的关系,了解之后在开发中规避一些性能问题。以下所探讨的事件循环皆指浏览器的事件循环,本文属于个人理解,如有不对欢迎提点。

1 事件循环

  提到事件循环,相信现在多数前端小白并不陌生了。首先事件循环分为宏任务和微任务。
宏任务:鼠标事件,键盘事件,网络事件,Html解析, 定时器等;
微任务:Dom变化,Promise;

  当这些事件发生的时候浏览器会在对应的事件队列(宏任务队列和微任务队列)中添加该事件处理器(回调函数)等待执行。而事件循环是一直循环着看事件队列中是否还有未处理的处理器,如果有的话就从队列首位取出处理器执行,执行完之后就移除对应处理器,就是这样一直循环着直到浏览器关闭为止,即使事件队列为空!但是在处理宏任务队列和微任务队列的方式不同。

在一次事件循环中只能处理一个宏任务,而需要处理所有微任务直到微任务队列清空
说明:事件循环是在主程序执行完之后就开始循环执行事件,主程序说简单点就是一个js文件从第一行执行到最后一行,主程序结束(此处说的是该应用就一个js文件)
举一个生活中的例子:
  银行中排队办理业务的时候,假设每个排队等待办理的普通客户是宏任务,而VIP客户是微任务。那么在银行早上开始上班的之前,柜员启动相应的机器(电脑,打印机等)属于主程序,等一切准备好之后就开始办理业务了。假如现在有10个普通用户(宏任务)在等待,开始处理第一个客户,处理完之后叫号第二个客户,开始处理。如果在处理第二个客户的时候突然来个一个vip用户,那么在处理为完普通用户之后就轮到处理VIP用户(微任务),如果在处理VIP用户的时候又来了个VIP用户的话3号普通客户就得等待处理完两个VIP客户。当然此处举例皆是只该银行就一个服务窗口,复合js单线程模式。

下图是一个完整的事件循环图:


1536470185534.jpg

2 浏览器渲染UI渲染

  浏览器通常会尝试60秒渲染一次页面,以达到每秒60帧(60fps)的速度, 60fps是检验前端体验是否流畅的标准,在动画里就意味着当浏览器没16ms一次刷新的话,体验是最流畅的,所有在整个页面生命周期中每一次事件循环都应该在16ms内完成。
但是在事件循环中可能会处理比较耗时的任务,那么渲染就会延后这样就会导致动画看上去不流畅,更严重的可能是页面处于假死状态等影响用户体验。
如果加上UI渲染的话上面的事件循环图是这样的


1536470913687.jpg

前端性能

  上面提到浏览器会尝试16ms重新渲染页面,那么假如在处理事件的时候遇到耗时任务的时候,浏览器将会延迟渲染页面,如果处理不当那将会导致体验差问题。接下来仔细分析以下整个流程。
  首先先排除为任务,只有宏任务的程序。现在有两个点击事件

<button id="bt1">btn1</button>
<button id="bt1">btn1</button>
<script>
  const btn1 = document.querySelector('#btn1')
  const btn2 = document.querySelector('#btn2')
  btn1.addEventListener('click', () => {
      console.log('运行需要8ms')
  })
  btn2.addEventListener('click', () => {
      console.log('运行需要10ms')
  })
</script>

假如主线程运行需要15ms,btn1事件处理器完成需要8ms, btn2需要10ms;那么整个流程是这样:


1.jpg

假如用户点击速度很快的时候,主线程运行期间在5ms和12ms时候分别点击了btn1,btn2按钮,触发点击事件,此时因为还处与主线程运行时间内,所有此时会向宏任务队列添加两个事件任务。当主线程运行到15ms时结束,此时浏览器可以重新渲染。渲染完之后进入事件循环,首先从任务队列中取出任务btn1事件处理器执行,此过程会耗时8ms,处理完之后结束一次事件循环,并移除事件处理器btn1。此时浏览器可以重新渲染,渲染完之后又进行到第二次事件循环,重复上述步骤,直到事件队列清空为止。
  如果事件循环中存在为任务的话流程如下:

<button id="bt1">btn1</button>
<button id="bt1">btn1</button>
<script>
  const btn1 = document.querySelector('#btn1')
  const btn2 = document.querySelector('#btn2')
  btn1.addEventListener('click', () => {
      // promise1
      promise.resolve().then( _ => {
         console.log('运行需要4ms')
      })
  })
  btn2.addEventListener('click', () => {
       // promise2
      promise.resolve().then( _ => {
         console.log('运行需要4ms')
       // promise3
         promise.resolve().then( _ => {
            console.log('运行需要4ms')
         })
      })
  })
</script>
2.jpg

其实只是在宏任务之后加入了一个时间段处理为任务.。在第一循环中处理btn1事件处理器时添加promise1到微任务队列中,在执行完btn1事件之后检查微任务队列发现有一个任务待处理,然后取出处理之后移除微任务promise1在循环微任务队列,发现以微任务队列为空,28ms时浏览器可以重新渲染(此时浏览器距离上次渲染相差12ms,渲染流畅)。在执行第二次事件循环的时候,在执行到微任务队列时,需要处理两个任务,耗时8ms,46ms时浏览器可以重新渲染(此时浏览器距离上次渲染相差18ms,渲染流畅)。
总结:如果在事件循环中有的任务比较耗时,会导致浏览器渲染fps变小,从而导致用户体验差。如向页面中插入10000个td节点的时候,如果是一次循环就appendChild一次,那相当于总共会有10000个微任务待执行,这样就会影响到浏览器渲染的fps,所有会出现点击页面没有反响的体验,这就是为什么插入多个节点需要拼接到一起一次性插入节点。了解事件循环和浏览器渲染之间到联系之后,以后开发多注意一些耗时任务到处理,到此分享结束。

相关文章

  • 聊一聊浏览器事件循环与前端性能

    在网上也看了不少关于javascript事件循环的文章,多数是以浏览器事件循环与nodejs中事件循环做对比,分析...

  • Python与Go异同之:for循环

    我们今天来聊一聊循环,其实一般我们在处理,前端传过来的列表时,我们需要记录前端传过来的顺序就需要用for循环处理一...

  • iOS 开发资源 - 收藏集 - 掘金

    聊一聊移动端调试那些事 - 前端 - 掘金 欢迎大家收看聊一聊系列,这一套系列文章,可以帮助前端工程师们了解前端的...

  • 浏览器渲染

    为什么需要明白浏览器渲染原理 页面的设计与实现之后,前端工程师就需要关注性能优化了。其中浏览器渲染机制是前端性能优...

  • 聊一聊前端存储那些事儿

    在web开发越来越复杂的今天,前端拥有的能力也越来越多。其中最重要的一项莫过于web存储。开发者们如果使用得当,这...

  • 聊一聊前端技术的发展

    前端的开始,要追溯到1994 年的时候,网景公司 (Netscape Communications) 推出了第一款...

  • 前端性能优化

    js性能小贴士——优化循环 前端网页与js性能优化 我总结的js性能优化的小知识 提高 web 应用性能之 Jav...

  • 前端性能 优化 大全

    js性能小贴士——优化循环 前端网页与js性能优化 我总结的js性能优化的小知识 提高 web 应用性能之 Jav...

  • 面试遇到的问题

    2019 web 前端面试总结(内附面经) js事件循环(EventLoop) 浏览器缓存 BFC js基本类型 ...

  • Nodejs事件循环机制(一)浏览器事件循环机制

    前言 事件循环可以理解为我们编写的js代码与浏览器或者node之间的一个桥梁 浏览器的事件循环是我们编写的js代码...

网友评论

本文标题:聊一聊浏览器事件循环与前端性能

本文链接:https://www.haomeiwen.com/subject/slcrgftx.html