前端精读:深入 JavaScript 事件循环(Event Loop)与异步编程原理

发布时间:2026/10/1 17:02:06

前端精读:深入 JavaScript 事件循环(Event Loop)与异步编程原理 文档技术博客教程【免费下载链接】weekly前端精读周刊。帮你理解最前沿、实用的技术。项目地址https://gitcode.com/GitHub_Trending/we/weekly点击查看免费下载事件循环Event Loop是 JavaScript 运行机制的核心内科它决定了setTimeout、Promise、fetch这些异步代码究竟在什么时机、以什么顺序被执行。本文以本期周刊精读的《Javascript 事件循环与异步》为骨架结合仓库内 Tasks, microtasks 精读、setState 原理精读 等系列文章帮你彻底打通 Call Stack、宿主环境、Microtask/Macrotask 之间的关系并给出一个 React 生命周期中可复现的异步setState实验读完即可在实际项目中写出更可靠、可预期的异步代码。1 引言为什么事件循环像一门内科本期精读的原始文章出自 sessionstack 的 How JavaScript works 系列该系列深入探讨 JS 内部原理并指出只有深入了解 JS 的工作方式才可能写出更好的代码。事件循环之于 JavaScript 开发者就像内科之于医生我们平时只关注外科创伤表层 API 用法却忽视内科问题——setTimeout、Promise这些基础概念如果只停留在浮层理解代码往往会在意想不到的地方出问题。对前端新人来说事件循环也几乎是面试的必考基础题。本文不展开原文的 Promise、async/await 部分聚焦 Event Loop 本身它如何与 Call Stack、宿主环境协作以及 Microtask 与 Macrotask 两种异步队列的差异。2 Event Loop 与 Call Stack、Web APIs 之间的关系2.1 三个角色各司其职在浏览器与 Node.js 中一次 JS 代码的执行涉及三个关键角色Call Stack调用栈所有同步代码的执行场所遵循后进先出LIFO规则。函数调用时入栈函数返回时出栈。Event Loop事件循环本篇文章的主角负责监控调用栈与异步队列在合适时机把异步回调推回调用栈执行。Web APIs宿主环境泛指浏览器提供的 APIDOM、fetch、定时器等或 Node.js 中的底层 C 能力。异步时机的判定由宿主环境负责而不由 JS 引擎负责。原文档用 16 张图演示了 5 行代码的完整执行过程核心结论可以概括为一句话任何同步代码只存在于 Call Stack 中只有异步代码不一定是回调才会进入 Event Loop 的队列。2.2 哪些代码属于异步典型的异步代码包括setTimeout() setInterval() Promise.resolve().then() fetch().then()这些异步代码在执行时不会直接进入 Call Stack而是进入 Event Loop 队列。当 JS 主线程Call Stack执行完毕、且异步时机已到Event Loop 才会把异步回调中的代码推入 Call Stack 执行。2.3 执行时机由宿主环境决定一个容易忽略的关键点异步代码何时开始执行是由宿主环境决定的而不是 JS 引擎。原因是当调用栈清空后JS 引擎本身不会再主动执行任何代码除非 Event Loop 队列中有内容被推送回 Call Stack。以fetch为例JS 调用浏览器发送请求后直到浏览器主动通知 JS请求已完成之前JS 无法干预任何环节——网络 IO 的调度完全在宿主环境浏览器/Node.js内部完成。这也解释了为什么setTimeout(fn, 0)并不是立即执行它只是把回调交给宿主环境的定时器线程计时倒计时结束后再把回调送入事件循环队列等待调用栈空闲。3 Microtask 与 Macrotask两种异步队列3.1 一维队列是不够的事件循环处理异步的方式分两种Macrotask宏任务setTimeout、setInterval之流。Microtask微任务Promise之流。原文档给出了一个非常直观的模型异步队列是周而复始循环执行的可以看作一个二维数组——横排是当前队列中的每一个函数纵排是每一个队列。Macrotask的方式是将执行函数添加到新的纵排新开一个队列Microtask将执行函数添加到当前执行到队列的横排插入到当前队列尾部。因此Microtask的插入是轻量的能最快被执行到——它不需要等待整条宏任务队列轮转只要当前宏任务结束后即可立刻消费。3.2 用真实输出验证执行顺序仓库内 Tasks, microtasks, queues and schedules 精读 给出了一个经典验证用例console.log(script start); setTimeout(function () { console.log(setTimeout); }, 0); Promise.resolve() .then(function () { console.log(promise1); }) .then(function () { console.log(promise2); }); console.log(script end);正确输出顺序是script start script end promise1 promise2 setTimeout原因如下同步脚本执行优先级最高script start、script end先后打印Promise的回调进入MicrotaskssetTimeout的回调进入Tasks一次事件循环中Microtasks 优先于 Tasks 执行Microtasks 执行期间新插入的 Microtaskpromise2会继续按顺序消费直到队列清空才会轮到 Tasks 中的setTimeout。更精细地看一个事件循环内的大致流程是执行同步代码 → 调用栈清空 → 清空 Microtasks 队列 → 浏览器可能执行渲染 → 执行下一个 Task。需要注意这里讨论的是一次 Event Loop 内立即执行的优先级与定时器的延迟时间如 3000ms、0ms不要混淆。4 实战精读componentWillMount 中异步 setState 的时灵时不灵理解了事件循环才能真正看懂 React 生命周期中那些看似诡异的行为。原文档作者在编写 dob-react 测试时发现componentWillMount函数在Microtask时机调用setState不会触发 rerender。4.1 只 render 一次的错误写法class Hello extends React.Component { async componentWillMount() { await immediate(() { this.setState({ a: 1 }); }); } render() { /**/ } }配套的immediate函数如下function immediate(fn) { return new Promise(resolve { fn(); resolve(); }); }这种写法下组件只会 render 一次。4.2 render 两次的正确写法如果再套一层Promise.resolve().then()让fn真正推迟到微任务时机执行function immediate(fn) { return new Promise(resolve Promise.resolve().then(() { fn(); resolve(); }) ); }此时组件会render 两次。4.3 原理与勘误原文档的 ps 补充非常重要第一种immediate写法其实是错误的——fn()在new Promise的 executor 中同步执行了await并没有真正推迟到微任务时机所以setState仍然发生在 React 生命周期内的同步渲染过程中setState被合并只触发一次 render。正确做法应使用Promise.resolve().then()将fn的调用真正推迟到 Microtask 时机。这个实验暴露了一个普遍问题在生命周期函数中异步setState的合并机制时而生效、时而不生效取决于setState被调用的时机落在同步执行阶段还是微任务阶段。这正是事件循环知识在真实业务中的价值——只有理解了 Microtask 的消费时机才能解释 React 为何合并状态更新以及何时合并会失效。4.4 从 React 实现看 setState 的调度入口要彻底理解上述现象可以顺藤摸瓜看 React 的状态更新链路。setState 做了什么精读 指出react包本身不包含 DOM 更新逻辑它只通过updater接口向渲染器react-dom、react-native等反向通信// A bit simplified setState(partialState, callback) { // Use the updater field to talk back to the renderer! this.updater.enqueueSetState(this, partialState, callback); }也就是说setState是否合并、何时触发 rerender最终由react-dom的渲染器在事件循环的某个时机同步任务还是微任务决定——这与本文事件循环的分析天然衔接。5 从事件循环到 React 调度与 Promise 工程实践事件循环并非孤立的面试知识点它向下支撑了框架的调度设计向上决定了业务异步代码的写法。5.1 React 的调度本质是事件循环上的时间分片Scheduling in React 精读 指出浏览器同一时间只能做一件事肉眼可识别的刷新频率约 60FPS意味着渲染、动画、响应用户输入必须在约 16ms 内完成。React 16 的 Concurrent 模式把同步渲染拆解为可中断的异步渲染利用浏览器事件循环的空闲分片分批执行任务本质上是站在事件循环之上做任务优先级调度。5.2 Promise 串行队列的底层原理用 Reduce 实现 Promise 串行执行精读 从另一个角度印证了事件循环模型reduce是同步执行的在一个事件循环内完成——它只是在内存中快速构造了一条 Promise 执行链function runPromiseByQueue(myPromises) { myPromises.reduce( (previousPromise, nextPromise) previousPromise.then(() nextPromise()), Promise.resolve() ); }reduce的作用就是把每个 Promise 完成后执行下一个的冗余队列代码折叠成一行生成。更现代的做法是用async/await改写为顺序等待async function runPromiseByQueue(myPromises) { for (let value of myPromises) { await value(); } }理解了两者的差异前者同步构造队列、后者异步逐项等待才能回答为什么Promise.all是并行、而await逐个写是串行这类问题。6 总结理解事件循环只是第一步。原文档作者坦言想写好稳健的业务代码需要三层能力理解内科知识Call Stack 只装同步代码异步代码进入 Event Loop 队列Microtask 优先于 Macrotask 消费读懂框架源码例如 React 的setState通过updater委托给渲染器实现调度与合并行为由渲染器在事件循环的时机决定保证不会忘异步时序知识是用到才想起来型知识建议通过类似本文第 4 节的可复现实验持续巩固。最后回到实践层面仓库中 Tasks, microtasks 精读 给出了两条务实建议业务逻辑不要巧妙依赖 Microtask 与 Task 执行顺序的微妙差异——不同浏览器Chrome、Firefox、Safari、Edge对任务顺序的实现存在差异依赖顺序的代码非常脆弱不要死记硬背调用顺序——只要记住核心规则即可推导一次事件循环中同步代码优先随后清空 Microtasks再处理 TasksMicrotasks 执行期间新插入的 Microtasks 会按序继续执行。类似的异步思维贯穿仓库多个主题处理异步异常的 捕获所有异步 error、并发场景下 async/await 是把双刃剑 中对顺序 await 拖慢并发的警示都可与本文的事件循环模型互相印证。当你下次面对为什么结果顺序不对为什么 setState 没生效时不妨先回到事件循环这个内科诊断一遍。赞分享文档技术博客教程【免费下载链接】weekly前端精读周刊。帮你理解最前沿、实用的技术。项目地址https://gitcode.com/GitHub_Trending/we/weekly点击查看免费下载相关推荐把静态图片变成动态视频一文搞懂 ComfyUI-WanVideoWrapper把静态图片变成动态视频一文搞懂 ComfyUI WanVideoWrapper 第一次做 AI 视频时我的目标很具体手上有一张红衣男子的照片想让它转头、人工智能大模型媒体生成JavaScript事件循环终极指南深入理解异步编程运行机制JavaScript事件循环终极指南深入理解异步编程运行机制 JavaScript事件循环是理解现代JavaScript异步编程的关键概念。作为一名JavaS教程示例工程JavaScript事件循环终极指南深入理解异步编程运行时机制JavaScript事件循环终极指南深入理解异步编程运行时机制 你是否曾经好奇为什么 setTimeout 并不总是准时执行为什么 Promise 比回调更文档教程上一篇ncmdump使用手记网易云NCM格式转换从拖拽到自动批量一篇讲透下一篇3步搞定NCM格式转换用ncmdump让加密音乐跨设备自由播放创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
延伸阅读

更多相关文章

2026/10/1 17:02:06

自治数据平台实战:自动化运维与性能调优的架构设计

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/1 16:57:06

北邮编译原理课程设计:词法分析到目标代码生成全流程拆解

简介:这份资源是北京邮电大学编译原理课程设计的完整实践项目,面向正在学习编译原理、需要动手实现编译器的计算机专业学生。项目以Pascal语言为源语言,覆盖词法分析、语法分析、语义分析、代码生成及符号表管理、错误处理、中间代码生成等核…

2026/10/1 16:57:06

HowToCook 闽南风味炒年糕:葱油提香的软糯主食完整实战指南

文档教程 【免费下载链接】HowToCook Programmers guide about how to cook at home. 项目地址: https://gitcode.com/GitHub_Trending/ho/HowToCook 点击查看 免费下载 导读 本篇技术指南基于 HowToCook 开源项目中的 炒年糕.md 菜谱文档,系统讲解以米…

2026/10/1 21:32:20

决策者的合规盾牌:在签字前锁定文件隐性风险

每一次文件签发、制度落地、政策发文,本质上都是一次正式决策背书。 流程完整、格式规范、签字齐全,并不等于风险为零。对政企高层决策者而言,真正棘手的问题不是“有没有流程”,而是能否在有限时间内,看清文件背后的真…

2026/10/1 21:32:20

聚合AI GEO性价比怎么样

苏州聚合增长信息科技有限公司简称聚合AI GEO,是国内专注于制造业生成式引擎优化(GEO)领域的企业级AI全域营销解决方案服务商,聚焦解决制造企业在AI搜索时代的信息错位、获客成本高痛点,为客户搭建从品牌曝光到商业成交的闭环转化体系。核心实…

2026/10/1 21:32:20

同行已经被AI推荐-纯文字

同行已经被 AI 推荐,现在开始做 GEO 还来得及吗? 来得及。 但有句话需要说透:越晚开始,真正增加的未必只是预算,而是品牌进入 AI 答案的时间成本。 现在就可以做一个简单测试。 打开豆包、DeepSeek、元宝、千问或 Kimi…

2026/10/1 21:32:20

聚合增长GEO性价比怎么样,服务评价好不好

站在AI搜索重构企业营销逻辑的关键转折点,制造业正在经历一场获客逻辑的深层变革:传统关键词营销的边际效益持续下滑,大模型生成内容的普及让用户获取信息的路径彻底改变,如何让品牌在AI搜索环境中被准确识别、建立信任、完成转化…

2026/10/1 21:32:20

聚合增长GEO靠谱吗,技术实力与创新能力如何

当AI开始替客户做决定,你的品牌被看见了吗深夜十一点,苏州一家机械制造企业的老板还坐在办公室里。他刚试着在豆包里输入工业撕碎机哪家厂商实力强,屏幕上给出的推荐名单里,没有自己的公司。可他分明记得,五年前&#…

2026/10/1 21:27:20

信号与系统入门指南:从卷积到傅里叶变换的核心概念与学习路径

信号与系统这门课,很多人第一次翻开教材就被那一堆卷积积分、傅里叶变换、拉普拉斯变换吓住了,觉得这又是一门靠背公式过关的数学课。但真正学进去的人会发现,它其实是在教你一套看待世界的底层视角——任何随时间变化的东西,都可…

2026/10/1 5:21:14

东莞市品牌网站建设报价常见报错与解决

东莞品牌网站建设报价单背后:一份保姆级建站教程避坑实录 网站做好了没人访问,这大概是很多老板最头疼的事。花了大几万做的品牌站,上线后流量惨淡,比路边摊还冷清。别急着骂外包公司,很多“东莞品牌网站建设报价”里藏着不少猫腻,比如用模板站冒充定制…

2026/10/1 17:09:46

如何划分训练/验证集:Spirula Studio五种eval_mode策略详解

如何划分训练/验证集:Spirula Studio五种eval_mode策略详解 【免费下载链接】spirula-studio Cross-vendor 3D Gaussian Splatting trainer - video to splat to mesh, Vulkan or CUDA. 项目地址: https://gitcode.com/GitHub_Trending/sp/spirula-studio Sp…

2026/10/1 10:48:55

SEO怎么推广速查手册新手避坑实战指南

SEO怎么推广速查手册新手避坑实战指南 模板网站太丑不够用?别急着加滤镜,那是治标不治本。很多老板盯着后台流量掉得眼红,却还在纠结首页Banner的圆角是不是3像素。这就像穿着西装去挖土,姿势不对,努力白费。我整理这份 速查手册…

还想了解更多?直接咨询顾问

免费诊断 + 免费方案 + 透明报价。

全国咨询热线400-8866-253
免费获取方案
☎咨询二维码 ☎ ↑