Appearance
浏览器事件循环的各个阶段(定时器、I-O 回调、空闲、渲染、微任务)
本节目标:理解“浏览器事件循环的各个阶段(定时器、I-O 回调、空闲、渲染、微任务)”的核心思路,并能把它用于实际开发或面试表达。 示例代码 _// 模拟 I/O 回调(例如网络请求)_function simulateAsyncIO(callback) { setTimeout(() => callback(), 0); // 模拟异步 I/O} _// 1. 定义宏任务和微任务_console.log('Script start'); _// 定时器阶段(setTimeout)_setTimeout(() => { console.log('setTimeout callback'); // 添加微任务 Promise.resolve().then(() => { console.log('setTimeout microtask'); });}, 0); _// I/O 回调阶段(模拟网络请求)_simulateAsyncIO(() => { console.log('I/O callback'); // 添加微任务 Promise.resolve().then(() => { console.log('I/O microtask'); });}); _// 空闲阶段(requestIdleCallback)_requestIdleCallback((deadline) => { console.log(
requestIdleCallback, timeRemaining: ${deadline.timeRemaining()});}); _// 渲染阶段(requestAnimationFrame)_requestAnimationFrame(() => { console.log('requestAnimationFrame callback'); // 修改 DOM,触发回流与重绘 document.body.style.background = 'lightblue';}); _// 微任务(Promise)_Promise.resolve().then(() => { console.log('Initial Promise microtask');}); console.log('Script end');
HTML(用于 DOM 操作) <!DOCTYPE html><html><body> <h1>Event Loop Demo</h1> <script src="script.js"></script></body></html>
事件循环阶段与执行顺序分析 JavaScript 的事件循环按以下阶段运行,每个阶段处理特定类型的任务,并在每个宏任务后清空微任务队列。我们将按照代码的实际执行顺序,结合事件循环阶段进行分析。 1. 同步代码执行(初始宏任务)
- 阶段:主线程(同步代码不是事件循环的一部分,但作为初始宏任务运行)。
- 执行:
- console.log('Script start') 输出:Script start。
- setTimeout 注册一个定时器回调,加入宏任务队列(定时器阶段)。
- simulateAsyncIO 调用 setTimeout(..., 0),注册 I/O 回调,加入宏任务队列(I/O 阶段)。
- requestIdleCallback 注册空闲回调,加入空闲任务队列。
- requestAnimationFrame 注册渲染回调,加入渲染任务队列。
- Promise.resolve().then(...) 注册微任务,加入微任务队列。
- console.log('Script end') 输出:Script end。
- 输出: Script startScript end
2. 微任务阶段(清空微任务队列)
- 阶段:在初始宏任务完成后,清空微任务队列。
- 执行:
- 执行 Promise.resolve().then(...) 的回调,输出:Initial Promise microtask。
- 输出: Initial Promise microtask
3. 定时器阶段(宏任务队列)
- 阶段:检查宏任务队列,执行到期的 setTimeout 回调。
- 执行:
- setTimeout 回调执行,输出:setTimeout callback。
- 在回调中,Promise.resolve().then(...) 注册一个新的微任务,加入微任务队列。
- 输出: setTimeout callback
4. 微任务阶段(清空微任务队列)
- 阶段:在定时器阶段的宏任务后,清空微任务队列。
- 执行:
- 执行 setTimeout 回调中的微任务,输出:setTimeout microtask。
- 输出: setTimeout microtask
5. I/O 回调阶段(宏任务队列)
- 阶段:处理宏任务队列中的 I/O 回调(simulateAsyncIO 的 setTimeout)。
- 执行:
- I/O 回调执行,输出:I/O callback。
- 在回调中,Promise.resolve().then(...) 注册一个新的微任务,加入微任务队列。
- 输出: I/O callback
6. 微任务阶段(清空微任务队列)
- 阶段:在 I/O 回调阶段后,清空微任务队列。
- 执行:
- 执行 I/O 回调中的微任务,输出:I/O microtask。
- 输出: I/O microtask
7. 渲染阶段
- 阶段:浏览器检查是否需要更新 DOM 和渲染。
- 执行:
- 执行 requestAnimationFrame 回调,输出:requestAnimationFrame callback。
- 执行 DOM 更新:document.body.style.background = 'lightblue',触发回流(布局计算)和重绘(样式应用)。
- 输出: requestAnimationFrame callback
- 备注:HTML 规范保证渲染阶段在定时器阶段之后,但实际渲染(绘制到屏幕)可能因浏览器优化而延迟。requestAnimationFrame 回调会在渲染前执行。
8. 空闲阶段
- 阶段:当浏览器空闲时,执行 requestIdleCallback 回调。
- 执行:
- requestIdleCallback 回调执行,输出:requestIdleCallback, timeRemaining: <some number>(具体时间取决于浏览器)。
- 输出: requestIdleCallback, timeRemaining: <some number>
- 备注:requestIdleCallback 的执行时机取决于浏览器调度,通常在渲染后或当浏览器空闲时运行。它的优先级低于其他阶段。
最终输出 Script startScript endInitial Promise microtasksetTimeout callbacksetTimeout microtaskI/O callbackI/O microtaskrequestAnimationFrame callbackrequestIdleCallback, timeRemaining: <some number>
关键点说明
- 事件循环顺序:
- 每个宏任务(定时器、I/O 回调)执行后,立即清空微任务队列。
- 渲染阶段(包括 requestAnimationFrame 和 DOM 更新)在定时器和 I/O 回调之后。
- 空闲阶段(requestIdleCallback)优先级最低,通常最后执行。
- 微任务优先级:
- 微任务(如 Promise.then)在每个宏任务后立即执行,确保微任务队列清空。
- 渲染阶段:
- requestAnimationFrame 在渲染前运行,适合动画相关操作。
- DOM 更新(如修改样式)可能触发回流和重绘,但实际屏幕绘制由浏览器决定。
- 空闲阶段:
- requestIdleCallback 适合低优先级任务(如后台数据处理),执行时间不保证。
注意事项
- 浏览器差异:requestIdleCallback 和渲染的实际执行时机可能因浏览器优化而略有不同。
- 异步模拟:simulateAsyncIO 使用 setTimeout 模拟 I/O,实际场景可能是 fetch 或文件操作。
- 微任务嵌套:微任务中注册新的微任务(如 setTimeout 回调中的 Promise)会在同一微任务阶段执行。
如果需要进一步优化代码或针对特定场景分析,请提供更多细节!
