浏览器渲染过程概述与事件循环基础
- 浏览器渲染主要包括构建DOM树、构建CSSOM(CSS对象模型)、生成渲染树、布局和绘制等阶段。在这个过程中,JavaScript的执行是穿插其中的,并且JavaScript代码的执行会受到事件循环(Event Loop)机制的控制。
- 事件循环机制管理着宏任务队列(Macro - Task Queue)和微任务队列(Micro - Task Queue)。宏任务队列包含如
script
(整体脚本)、setTimeout
、setInterval
、I/O
操作(像Ajax请求)等任务;微任务队列包含Promise.then()
、MutationObserver
、process.nextTick
(在Node.js环境下)等任务。
执行顺序规则
- 初始脚本执行作为首个宏任务:当浏览器开始加载一个网页时,解析HTML遇到的第一个
script
标签内的代码被视为第一个宏任务。在这个宏任务执行过程中,如果遇到同步代码,会直接执行;如果遇到异步宏任务(如setTimeout
),会将其回调函数添加到宏任务队列的末尾;如果遇到微任务(如Promise.then
),会将其回调函数添加到微任务队列。 - 宏任务执行间隙检查微任务队列:当一个宏任务执行完毕后,浏览器不会立即执行下一个宏任务,而是先检查微任务队列。如果微任务队列中有任务,会按照先进先出(FIFO)的顺序依次执行微任务队列中的所有任务。只有当微任务队列清空后,才会从宏任务队列中取出下一个宏任务并执行。
- 初始脚本执行作为首个宏任务:当浏览器开始加载一个网页时,解析HTML遇到的第一个
具体示例说明
- 假设我们有以下代码:
```html
<!DOCTYPE html>
- 假设我们有以下代码:
```
- 执行过程如下:
- 首先,浏览器将
script
标签内的代码作为第一个宏任务(Macro Task 1)开始执行。- 打印
Script start (Macro Task 1)
。 - 遇到
promise1
,将其.then
回调函数添加到微任务队列(这是微任务1)。 - 遇到
setTimeout
,将其回调函数添加到宏任务队列(这将是Macro Task 2)。 - 打印
Script end (Macro Task 1)
,此时Macro Task 1执行完毕。
- 打印
- 接着,因为宏任务1结束,浏览器检查微任务队列。发现有微任务1,开始执行微任务队列中的任务。
- 执行
promise1
的.then
回调函数,打印Promise data 1
,然后更新id
为content
的DOM元素的文本内容,将Promise 1 data added.
添加进去。此时微任务1执行完毕。
- 执行
- 微任务队列清空后,浏览器从宏任务队列中取出下一个宏任务(Macro Task 2)执行。
- 执行
setTimeout
的回调函数,打印Timeout callback (Macro Task 2)
。 - 遇到
promise2
,将其.then
回调函数添加到微任务队列(这是微任务2)。此时Macro Task 2中的同步代码执行完毕。
- 执行
- 因为宏任务2中的同步代码执行完毕,浏览器再次检查微任务队列。发现有微任务2,开始执行微任务队列中的任务。
- 执行
promise2
的.then
回调函数,打印Promise data 2
,然后更新id
为content
的DOM元素的文本内容,将Promise 2 data added.
添加进去。此时微任务2执行完毕。
- 执行
- 首先,浏览器将
在浏览器渲染过程中,这种宏任务和微任务的执行顺序确保了一些对及时性要求较高的操作(如微任务中的DOM更新)能够及时执行,同时又能合理地安排一些可以延迟执行的操作(如宏任务中的定时器回调),从而使浏览器能够在处理JavaScript代码的同时,尽可能流畅地进行渲染工作。