Web 浏览器之事件循环 Event Loop
一、
要了解事件循环Event Loop,首先我们必须明白以下几点
1、javascript是一门单线程语言
在最新的HTML5中提出了Web-Worker,但javascript是单线程这一核心仍未改变。
是单独开启了一条js线程,即workerjs线程和js主线程,
二者相互独立,通过postmessage通信,worker只是负责复杂运算,但不能操作DOM,有诸多限制
有了多线程的形,但不具备多线程的神,不具备线程间通信的能力
2、JS和元素渲染在同一个线程中工作
EventLoop在其周期内执行任务,我们的JS代码必须以某种方式与DOM配合使用。
获取元素的大小,添加属性,绘制一些弹出窗口,等等。它应该使界面活跃起来。它为元素图形 添加了一些限制。
在其中一个线程中运行两个线程来执行JS,在另一个线程中使用CSS进行渲染,这很复杂,因为它需要大量的代码同步,否则可能会导致执行不一致。
这就是为什么JS和元素渲染在同一个线程中工作。
这意味着我们应该在模式中添加“渲染”。因为这不是一个单一的操作,所以最好使用单独的队列。我们称之为渲染队列
3、屏幕更新(事件循环与帧密不可分)
事件循环不仅执行JS代码,还计算新的帧。
浏览器试图尽快在页面上显示更改。我们确实有一些限制:
硬件限制:屏幕刷新率;
软件限制:操作系统、浏览器、节能设置等。
大多数现代设备(和应用程序)支持每秒60帧。
大多数浏览器都试图以这种特定的速度更新屏幕。
因此,我们将在本文中使用60 FPS,但最好记住,确定的速率可能会有所不同。
对于我们的事件循环来说,这意味着如果我们想要保持60FPS,我们的任务有16.6ms的时隙。
可以简单理解为我们以60 FPS在循环
4、队列和栈
队列是先进先出,栈是先进后出
5、微任务、宏任务、DOM渲染
宏任务:DOM渲染后触发,如:setTimeout,setInterval,Ajax,DOM事件
微任务:DOM渲染前触发,如:Promise,async/await
微任务执行时机要比宏任务要早
所以我们可以理解为事件循环(Event Loop)有如下图所示的一个无限循环,有无数的任务等着去执行
二、
有了这无数的任务,我们怎么来处理这些任务呢?他们必然是有优先级的,要在不同的队列去执行
微任务队列,宏任务队列,渲染队列
事件循环就会从不同的队列中取对应的任务
1、渲染队列(Render Queue)
每个帧渲染可以分为几个阶段,每个阶段可分为子阶段,我们大致可以如下图所示理解这个渲染
位置大小-->样式-->布局-->绘制-->合成
优先级最高
2、微任务队列(Microtask Queue)
顾名思义这个里面存放的都是些微任务,什么是微任务(MicroTask)?
Promises 或者 MutationObserver(观察的是DOM) callbacks等
微任务是特别的,主要特点是,一旦调用堆栈变空,它们就会被执行,优先级高
3、宏任务队列(Macrotask Queue)
顾名思义这个里面存放的都是宏任务,什么是宏任务(MacroTask也叫Task)?
Web API、Script代码块、UI样式的计算等
宏任务的特点就是耗时比较久,优先级低,排队等着执行
4、调用堆栈(Call Stack)
调用堆栈是一个列表,显示当前正在调用哪些函数,以及当前函数完成执行后将在何处进行转换。
故我们可以用下图简单示意
参考资料:
https://xnim.me/blog/javascript-browser-event-loop-layout-paint-composite-call-stack