深入解析 JavaScript 浏览器事件循环机制:从进程到宏微任务
本文详细解析 JavaScript 在浏览器中的事件循环机制。从进程与线程的基础概念入手,阐述浏览器内核中 GUI 渲染、JS 引擎等线程的协作与互斥关系。重点讲解同步与异步任务的划分,以及宏任务(Macro-task)和微任务(Micro-task)在 Event Loop 中的执行优先级与顺序,通过示例代码剖析控制台输出结果,帮助开发者彻底理解 JS 单线程模型下的异步处理逻辑。
首先梳理相关的基础概念。
进程与线程的基础概念
进程与线程的基本定义
进程是系统分配独立资源的单位,也是 CPU 资源分配的基本单元,它由一个或多个线程组成。
线程是进程内的执行流,作为 CPU 调度和分派的基本单位。同一进程下的多个线程共享该进程的资源。
浏览器内核的多线程架构
浏览器内核架构
浏览器采用多进程架构,每个标签页通常对应一个独立的进程(尽管多个空白标签页可能会合并到同一个进程中)。浏览器内核,即浏览器渲染进程,是多进程架构中的一种关键进程。
浏览器内核内部运行着多个线程,协同工作。
GUI 渲染线程
- 该线程负责页面的渲染工作,包括解析 HTML 和 CSS 以构建 DOM 树等。当页面发生重绘或回流时,会触发该线程执行。
- GUI 渲染线程与 JS 引擎线程互斥。当 JS 引擎线程正在执行代码时,GUI 渲染线程会被挂起,GUI 的更新操作会被放入任务队列中,等待 JS 引擎线程空闲后继续执行。
JS 引擎线程
- 该线程以单线程模式工作,主要负责解析和执行 JavaScript 脚本。
- 它与 GUI 渲染线程互斥。如果 JS 执行时间过长,会导致页面阻塞,影响用户体验。
事件触发线程
当事件满足触发条件时,该线程会将对应的回调函数添加到任务队列的队尾,等待 JS 引擎处理。
定时器触发线程
- 浏览器的定时计数器并非由 JS 引擎计数,因为 JS 阻塞会导致计时不准确。
- 浏览器开启专门的定时器触发线程进行计时。计时结束后,会将对应的事件添加到任务队列中,等待 JS 引擎处理。
HTTP 请求线程
- 发起 HTTP 请求时,会开启一条独立的请求线程。
- 当请求完成并获取结果后,会将请求的回调函数添加到任务队列中,等待 JS 引擎处理。

JavaScript 引擎的单线程特性
JavaScript 引擎的单线程特性
JavaScript 引擎是单线程的,这意味着每次只能执行一项任务,其他任务必须按顺序排队等待。只有当前任务执行完毕后,才会执行下一个任务。
HTML5 提出了 Web-Worker API,旨在解决页面阻塞问题,但这并未改变 JavaScript 单线程的本质。开发者应了解 Web-Worker 的使用场景。
事件循环机制概述
JavaScript 事件循环机制概述
JavaScript 的事件循环机制分为浏览器环境和 Node.js 环境,两者的实现技术不同。浏览器的 Event Loop 由 HTML 规范定义,而 Node.js 的 Event Loop 由 libuv 库实现。本文主要讨论浏览器环境下的机制。
JavaScript 拥有一个主线程(main thread)和一个调用栈(call-stack,也称执行栈)。所有任务都会被放入调用栈,等待主线程执行。
JS 调用栈的工作原理
JS 调用栈是一种后进先出(LIFO)的数据结构。当函数被调用时,它会被压入栈顶;执行完成后,该函数从栈顶弹出,直到栈被清空。
同步与异步任务的执行流程
同步任务与异步任务的区分
在 JavaScript 单线程模型中,任务分为同步任务和异步任务。同步任务按顺序在调用栈中排队,等待主线程执行。异步任务则在产生结果后,将注册的回调函数添加到任务队列(消息队列)中,等待主线程空闲(即调用栈清空)时,从队列读取并压入栈中执行。任务队列遵循先进先出(FIFO)原则。
Event Loop 运行机制
当调用栈中的同步任务全部执行完毕,栈被清空,表示主线程空闲。此时,Event Loop 机制会从任务队列中按顺序读取一个任务放入栈中执行。每次栈清空后,都会检查任务队列是否有任务,若有则读取执行,这种“读取-执行”的循环过程构成了事件循环。


定时器的行为机制
定时器会开启一条定时器触发线程进行计时。在等待指定时间后,定时器会将事件放入任务队列,等待主线程读取并执行。
定时器指定的延时毫秒数并不绝对准确。因为定时器仅在指定时间后将事件放入任务队列,必须等待同步任务和现有任务队列中的事件全部执行完毕后,才会读取定时器事件到主线程执行。如果中间存在耗时较长的任务,就无法保证在指定时间精确执行。
宏任务与微任务的执行顺序
宏任务与微任务的细分
除了广义的同步和异步任务,JavaScript 单线程中的任务可以细分为宏任务(macro-task)和微任务(micro-task)。
宏任务(macro-task)包括:script(整体代码)、setTimeout、setInterval、setImmediate、I/O、UI rendering。
微任务(micro-task)包括:process.nextTick、Promises、Object.observe、MutationObserver。
console.log(1);
setTimeout(function() {
console.log(2);
})
var promise = new Promise(function(resolve, reject) {
console.log(3);
resolve();
})
promise.then(function() {
console.log(4);
})
console.log(5);
在示例代码中,setTimeout 和 Promise 被称为任务源。来自不同任务源注册的回调函数会被放入不同的任务队列中。
有了宏任务和微任务的概念后,JavaScript 的执行顺序是怎样的?是宏任务先执行还是微任务先执行?
在第一次事件循环中,JavaScript 引擎会将整个 script 代码作为一个宏任务执行。执行完成后,会检测本次循环中是否存在微任务。如果存在,则依次从微任务的任务队列中读取并执行完所有微任务,然后再读取宏任务任务队列中的任务执行,接着再执行所有的微任务,如此循环。JS 的执行顺序遵循每次事件循环中的“宏任务-微任务”模式。
- 以上述示例代码为例,在第一次事件循环中,整段代码作为宏任务进入主线程执行。
- 遇到 setTimeout 时,会在等待指定时间后,将回调函数放入宏任务的任务队列中。
- 遇到 Promise 时,会将 then 函数放入微任务的任务队列中。
- 整个事件循环完成后,会检测微任务的任务队列中是否存在任务,如果存在则执行。
- 第一次循环的结果打印顺序为:1, 3, 5, 4。
- 接着,从宏任务的任务队列中按顺序取出一个宏任务到栈中让主线程执行。在这次循环中,宏任务是 setTimeout 注册的回调函数。执行完该回调函数后,发现本次循环中不存在微任务,准备进行下一次事件循环。
- 检测到宏任务队列中已没有要执行的任务,事件循环结束。
- 最终的控制台输出结果为:1, 3, 5, 4, 2。
以上是对 JavaScript 浏览器事件循环机制的详细解析,希望对大家有所帮助。如有任何疑问,欢迎留言,小编会及时回复。感谢大家对本站的支持!
D3.js 处理大规模数据点的性能优化策略
2026-11-05 15:37:25
JS实现网页摄像头实时扫码:结合getUserMedia与jsQR教程
2026-11-05 15:27:28
D3.js v5 迁移到 v7 的 API 变更与注意事项
2026-11-05 11:57:17
使用 animate.js 实现多元素协同的序列动画
2026-11-04 22:27:53
JS拖拽排序时怎么添加平滑动画效果?FLIP技术实战
2026-11-04 10:12:22
