
SONA
weiping8/8/20264596
某某项目是面向全球中文开发者的技术内容分享与交流平台。我们通过技术文章、沸点、课程、直播等产品和服务,打造一个激发开发者创作灵感,激励开发者沉淀分享,陪伴开发者成长的综和服务
核心回答
JavaScript 的事件循环(Event Loop)是处理异步代码执行的核心机制。由于 JS 是单线程的,它通过事件循环实现非阻塞的异步行为,确保主线程不被耗时操作阻塞。以下是关键点:
1. 核心组成
- 调用栈(Call Stack):跟踪当前执行的函数,后进先出(LIFO)。
- Web APIs:浏览器提供的异步 API(如
setTimeout,fetch),处理完任务后会将回调推入任务队列。 - 任务队列(Task Queue):分为 宏任务队列(MacroTask Queue)和 微任务队列(MicroTask Queue)
- 宏任务:
setTimeout、setInterval、I/O 操作、UI 渲染。 - 微任务:
Promise.then、MutationObserver、queueMicrotask。
- 宏任务:
2. 事件循环的工作流程
- 执行同步代码:先处理调用栈中的同步代码。
- 处理异步任务:遇到异步操作(如
setTimeout),交给 Web APIs 处理,完成后将回调推入对应队列。 - 轮询队列:
- 清空微任务队列:每轮事件循环结束前,必须执行完所有微任务。
- 执行一个宏任务:从宏任务队列中取出一个任务执行。
- 重复循环:直到所有队列为空。
3. 关键规则
- 微任务优先级 > 宏任务:同一轮循环中,微任务会在宏任务之前执行。
- 渲染时机:浏览器可能在宏任务之间进行 UI 渲染。
示例代码分析
console.log("1"); // 同步代码 setTimeout(() => console.log("2"), 0); // 宏任务 Promise.resolve().then(() => { console.log("3"); // 微任务 }); console.log("4"); // 同步代码 // 输出顺序为 1 → 4 → 3 → 2
更多详解
为什么 js 是单线程?
js 作为主要运行在浏览器的脚本语言,js 主要用途之一是操作 DOM。
在 js 高程中举过一个栗子,如果 js 同时有两个线程,同时对同一个 dom 进行操作,这时浏览器应该听哪个线程的,如何判断优先级?
主线程
主线程循环:即主线程会不停的从执行栈中读取事件,会执行完所有栈中的同步代码。
当遇到一个异步事件后,并不会一直等待异步事件返回结果,而是会将这个事件挂在与执行栈不同的队列中,我们称之为任务队列(Task Queue)。
js 异步执行的运行机制。
- 所有任务都在主线程上执行,形成一个执行栈。
- 主线程之外,还存在一个"任务队列"(task queue)。只要异步任务有了运行结果,就在"任务队列"之中放置一个事件。
- 一旦"执行栈"中的所有同步任务执行完毕,系统就会读取"任务队列"。那些对应的异步任务,结束等待状态,进入执行栈并开始执行。
- 主线程不断重复上面的第三步。
宏任务与微任务
异步任务分为 宏任务(macrotask) 与 微任务 (microtask),不同的 API 注册的任务会依次进入自身对应的队列中,然后等待 Event Loop 将它们依次压入执行栈中执行。
- 宏任务(macrotask) => script(整体代码)、setTimeout、setInterval、UI 渲染、 I/O、postMessage、 MessageChannel、setImmediate(Node.js 环境)
- 微任务(microtask) => Promise、 MutaionObserver、process.nextTick(Node.js 环境)
Event Loop(事件循环)
Event Loop(事件循环)中,每一次循环称为 tick, 每一次tick的任务如下:
- 执行栈选择最先进入队列的宏任务(通常是script整体代码),如果有则执行
- 检查是否存在 Microtask,如果存在则不停的执行,直至清空 microtask 队列
- 更新render(每一次事件循环,浏览器都可能会去更新渲染)
- 重复以上步骤
宏任务 > 所有微任务 > 宏任务,如下图所示: