JavaScript的事件循环-EventLoop
JavaScript的事件循环是一道很古老很常见的面试题,也是前端开发必须明白的一个知识点。事件循环的说法很多,很早的一种说法的宏任务
和微任务
去进行轮询处理异步任务。后面开始有线程
和队列
的说法。说是浏览器的渲染主线程
去按照一定的优先级去调用执行多个队列
内的任务,javascript代码在执行时,有同步代码直接在渲染主线程
内执行,异步代码根据相应的优先级放进对应的队列
等待渲染主线程
的执行。
什么是浏览器的渲染主线程,它都干什么?
浏览器的渲染主线程是浏览器中负责处理页面渲染和交互的核心线程之一。它负责执行各种任务,包括处理 DOM(文档对象模型)操作、CSS 解析、布局计算、绘制页面元素以及处理用户输入等。以下是渲染主线程需要处理的一些主要任务:
-
HTML 解析: 当浏览器接收到 HTML 文档时,渲染主线程负责解析 HTML,并将其转换成 DOM 树。
-
CSS 解析: 解析 CSS 样式表,构建 CSSOM 树(CSS Object Model),用于确定每个元素的样式信息。
-
渲染树的构建: 结合 DOM 树和 CSSOM 树,生成渲染树(Render Tree),渲染树中包含了需要渲染的 DOM 元素以及其对应的样式信息。
-
布局计算: 根据渲染树中每个元素的尺寸、位置和样式等信息,计算出页面中各个元素的布局(也称为重排或回流)。
-
绘制: 将页面元素根据其布局信息绘制到屏幕上,生成像素信息。
-
交互处理: 监听用户的交互事件,如鼠标点击、滚动、键盘输入等,并相应地触发相应的事件处理函数。
-
动画处理: 如果页面中包含 CSS 动画或 JavaScript 实现的动画,渲染主线程需要及时更新页面状态,实现动画效果。
-
网络请求和资源加载: 渲染主线程也负责发起网络请求,获取页面所需的外部资源,如图片、脚本、样式表等,并在资源加载完成后将其渲染到页面上。
这些任务在渲染主线程中按照优先级顺序执行,以确保页面能够尽快呈现给用户并保持良好的交互响应性。然而,需要注意的是,如果某个任务耗时过长或阻塞了主线程,可能会导致页面渲染停顿,用户体验下降。因此,开发者需要注意优化页面性能,尽量减少主线程的阻塞和负载。
浏览器渲染主线程事件循环相关主要需要知道哪些队列?以及这些队列的优先级?
在 Chrome 浏览器的事件循环中,存在多个队列,其中至少包括以下三种队列:延时队列 、交互队列 和微队列。让我们详细了解它们的优先级以及 JavaScript 中哪些操作对应于这些队列。
-
延时队列:
- 用于存放计时器到达后的回调任务。
- 优先级:中
- 对应的 JavaScript 操作:
setTimeout
和setInterval
。
-
交互队列:
- 用于存放用户操作后产生的事件处理任务。
- 优先级:高
- 对应的 JavaScript 操作:例如点击事件、键盘事件、鼠标事件等。
-
微队列:
- 用于存放需要尽快执行的任务。
- 优先级:最高
- 对应的 JavaScript 操作:主要是使用
Promise
和MutationObserver
。
现在,让我们来看一下这些队列的处理顺序:
- 当 JavaScript 代码执行时,遇到异步任务(例如
setTimeout
、用户操作、Promise 等)时,这些任务会被添加到相应的队列中。 - 首先,微队列中的任务会被优先执行,确保这些任务在下一次事件循环之前得到处理。
- 接下来,交互队列中的任务会被执行,处理用户操作产生的事件。
- 最后,延时队列中的任务会被执行,即计时器到达后的回调。
总之,JavaScript 的事件循环机制保证了异步任务的有序执行,避免了阻塞主线程,从而保持了浏览器的流畅运行。
代码示例
理解延时队列、交互队列和微队列的优先级执行顺序是非常重要的,因为它关系到 JavaScript 中异步任务的执行顺序。
javascript
console.log('Start');
setTimeout(() => console.log('Timeout 1'), 0);
Promise.resolve().then(() => console.log('Microtask 1'));
document.addEventListener('click', () => console.log('Click event'));
console.log('End');
执行顺序分析:
- 同步任务:执行
console.log('Start')
和console.log('End')
,打印 "Start" 和 "End"。 - 微任务队列:执行
Promise.resolve().then(() => console.log('Microtask 1'))
,打印 "Microtask 1"。 - 延时队列:执行
setTimeout(() => console.log('Timeout 1'), 0)
,打印 "Timeout 1"。 - 交互队列:等待用户点击,当用户点击时,执行
document.addEventListener('click', () => console.log('Click event'))
,打印 "Click event"。