web前端技术分享
事件循环

一、浏览器设定事件循环机制的起因

       浏览器在执行页面代码的过程中,可能接收到各种任务,比如用户点击事件、定时器完成回调、网络请求完成回调等等。我们可以思考一下,当这些任务同时需要浏览器去执行的时候,浏览器会采取什么策略去执行呢?

       如果你有学习过其它的一些高级编程语言如C、Java等,你可能会考虑到我可以采用多线程来执行这些任务,来一个js任务我就开一个线程,来一个js任务我就开一个线程。但是浏览器却恰恰没有选择通过多线程来解决这个问题。在我看来可能有以下原因:

  1. dom竞争,当两个线程同时操作同一个dom可能会在前端界面上呈现出很多的怪异行为,在开发过程中为了避免dom竞争,开发者可能需要付出 大量的额外工作。
  2. 早期的前端页面都比较简单,在js设计之初,页面其实都并不复杂,而哪怕到了今天,前端工程变得越来越复杂,对于大多数页面来说,在页面运行的整个生命周期中,发生多个任务同时需要浏览器执行的占比其实并不多,如果频繁的开辟线程,销毁线程,反而可能会带来不必要的性能开销,和在进程管理上所要付出的巨额工作量。

二、事件循环机制

       基于以上论述,浏览器需要采用单线程的策略来执行页面生命周期中的各种任务。于是就有了任务队列。对啊,浏览器可能同时就是有多个任务要执行,但是我有只能单线程的去执行,那就只好像银行柜台一样,让js任务们排队,我按照一定的优先级来逐个去执行你们了。而我们所说的事件循环,在这里起到的就是类似柜台柜员的角色。事件循环会不停的访问任务队列,如果队列中没有任务,在一定间隔后再次重新访问任务队列。如果队列中有任务,则从中取出任务执行,待该任务执行完成后,再重新循环访问任务队列,如果队列中有多个任务则按照一定的优先级进行选取执行

三、任务的优先级

        但凡学习过一段时间的前端开发的同学,我想都应该听说过“微任务优先级高于宏任务”这句话。在实际的开发中了解这一点足以应付绝大多数的开发场景了,但是这里我还要进一步做一些补充:

  1. 微任务队列如果不为空,事件循环将永远从微队列中选取任务,直到微任务队列为空,事件循环才会从宏任务队列中选取,不管宏队列中的任务已经等待了多长时间。
  2. 宏任务队列中同样存在一定的优先级,一般来讲,用户的交互任务,如:点击、输入等操作。相比于其它宏任务如:定时器任务。享有更高的优先级。
  3. 浏览器完成页面渲染的渲染任务同样位于任务队列中通过实践循环进行调度。

四、开发者注意事项

1、长任务阻塞

      上文讲到,浏览器通过事件循环处理各个任务。如果队列中有任务,则从中取出任务执行,待该任务执行完成后,再重新循环访问任务队列。也就是说,如果这个被选取的任务一直无法执行完成,则事件循环将不再工作,将不会有新的任务被执行。这就是所说的长任务阻塞。当一个要耗费很长时间的js任务进入任务队列并被事件循环选取执行后,在这个任务整个的执行过程中,页面将不再响应任何东西,按钮将无法被点击。不仅如此,连页面渲染任务也将同样被阻塞,连页面上的gif图都不会再更新帧。

      为防止该问题的发生,开发者应当,尽可能的避免长任务的出现,如果无可避免,可以考虑,web worker或其它技术来解决,以保证页面的流畅性。

总结:单线程执行,微任务优先,长任务阻塞。