当前位置: 首页 > news >正文

JS循环机制

JavaScript 的事件循环 (Event Loop) 是其实现非阻塞异步编程的核心机制,尽管 JS 是单线程的。它通过一种高效的任务调度方式,处理定时器、网络请求、用户交互等异步任务。

事件循环中的关键角色:

角色职责常见类型
调用栈 (Call Stack)执行同步代码,后进先出 (LIFO)。一旦开始执行直到栈空,才会处理其他任务。同步执行的函数、语句
宏任务 (Macrotask)由浏览器或 Node.js 环境提供,每次事件循环通常只取一个执行。setTimeout, setInterval, setImmediate (Node), I/O 操作, UI 渲染, DOM 事件回调, script 整体代码
微任务 (Microtask)在当前宏任务结束后、下一个宏任务开始前立即执行,并且会清空整个微任务队列。优先级高于宏任务。Promise.then/catch/finally, MutationObserver, queueMicrotask, process.nextTick (Node)
事件循环 (Event Loop)负责协调这些组件,不断检查调用栈和任务队列。

🔄 事件循环的工作流程

事件循环的运行机制可以概括为以下步骤:

  1. 执行同步代码:从宏任务队列中取出一个任务(最初是整体 script 代码)执行其中的同步代码,这会填入调用栈。
  2. 清空微任务队列:当前宏任务的同步代码执行完毕(调用栈空)后,事件循环会立即处理微任务队列,依次执行所有微任务。如果在执行微任务的过程中又产生了新的微任务,新微任务也会在当前轮次中被执行,直到微任务队列彻底清空
  3. UI 渲染(如有需要):浏览器可能会在此处进行页面渲染(计算样式、布局、绘制等)。
  4. 取下一个宏任务:从宏任务队列中取出下一个宏任务执行,然后回到第 2 步,开始新一轮的循环。

📖 代码执行顺序实战

记住这个核心原则:同步代码 > 微任务 > 宏任务

基础示例
console.log('1 - 开始'); // 同步setTimeout(() => {console.log('2 - 定时器回调'); // 宏任务
}, 0);Promise.resolve().then(() => {console.log('3 - Promise回调'); // 微任务
});console.log('4 - 结束'); // 同步// 输出顺序:
// 1 - 开始
// 4 - 结束
// 3 - Promise回调
// 2 - 定时器回调

过程分析

  1. 执行整体代码(宏任务),输出同步的 1 - 开始4 - 结束
  2. 遇到 setTimeout,其回调函数被放入宏任务队列
  3. 遇到 Promise.then,其回调函数被放入微任务队列
  4. 当前宏任务执行完毕,开始清空微任务队列,输出 3 - Promise回调
  5. 微任务队列清空后,从宏任务队列中取出 setTimeout 的回调并执行,输出 2 - 定时器回调
嵌套示例
console.log('Script start'); // 同步setTimeout(() => {console.log('setTimeout'); // 宏任务2Promise.resolve().then(() => {console.log('Promise inside setTimeout'); // 微任务3});
}, 0);Promise.resolve().then(() => {console.log('Promise1'); // 微任务1setTimeout(() => {console.log('setTimeout inside Promise'); // 宏任务3}, 0);
});console.log('Script end'); // 同步// 输出顺序:
// Script start
// Script end
// Promise1
// setTimeout
// Promise inside setTimeout
// setTimeout inside Promise

过程分析

  1. 执行同步代码,输出 Script startScript end
  2. 清空微任务队列,执行 Promise1 的输出,并注册一个新的 setTimeout(宏任务3)。
  3. 执行下一个宏任务(setTimeout),输出 setTimeout,并在其内部将一个 Promise.then(微任务3)加入微任务队列。
  4. 当前宏任务(setTimeout)执行完毕后,再次清空微任务队列,输出 Promise inside setTimeout
  5. 执行再下一个宏任务(setTimeout inside Promise),输出 setTimeout inside Promise

⚠️ 注意事项与性能优化

  1. 避免阻塞主线程:长时间的同步任务会阻塞事件循环,导致页面无响应。对于复杂计算,可考虑使用 Web Workers 或将任务拆分成小块,利用 setTimeoutrequestAnimationFrame 分时执行。
  2. 警惕微任务风暴:在微任务中无限递归地添加微任务会导致事件循环永远无法进入下一个宏任务,页面会卡死。
    // 错误示例:这将导致页面卡死
    function infiniteMicrotask() {Promise.resolve().then(infiniteMicrotask);
    }
    infiniteMicrotask();
    
  3. 理解 async/awaitasync/await 本质上是基于 Promise 的语法糖。await 语句之后的代码相当于被包装在 Promise.then 中,因此是微任务
    async function example() {console.log('Async function start');await Promise.resolve();console.log('After await'); // 这行代码是微任务!
    }
    
  4. 浏览器与 Node.js 的差异:虽然在现代浏览器和高版本 Node.js(v11+)中,事件循环的基本顺序(一个宏任务后清空所有微任务)已趋于一致,但 Node.js 的事件循环阶段划分更为复杂(如 setImmediateprocess.nextTick 的特有行为),在跨环境开发时需留意。

💎 总结

JavaScript 事件循环的机制可以概括为:

  • 同步代码立即执行
  • 异步任务分类处理:微任务(如 Promise)在当前宏任务结束后立即执行;宏任务(如 setTimeout)等待下一轮事件循环。
  • 微任务优先级高于宏任务。
  • 每个宏任务后都伴随着一次微任务队列的清空

理解事件循环,能让你更从容地应对异步编程,写出更可靠、性能更好的代码。

http://www.dtcms.com/a/362033.html

相关文章:

  • IS-IS的原理
  • Java超卖问题
  • MySQL安装与使用指南
  • 【读论文】量子关联增强双梳光谱技术
  • 力扣404 代码随想录Day15 第三题
  • 故障排查指南:理解与解决 “No route to host“ 错误
  • NOSQL——Redis
  • MySQL基础知识保姆级教程(四)视图与约束
  • 浅谈中断控制器:从 IRQ 到 IRR、IMR、In-Service Register
  • 软考-操作系统-错题收集(3)文件系统的索引节点结构
  • 【前端】《手把手带你入门前端》前端的一整套从开发到打包流程, 这篇文章都会教会你;什么是vue,Ajax,Nginx,前端三大件?
  • ComPE for win 纯净的PE系统
  • 软考中级数据库系统工程师学习专篇(67、数据库恢复)
  • Spring Security 深度学习(四): 会话管理与CSRF防护
  • 2025 数字化转型期,值得关注的 10 项高价值证书解析
  • Linux笔记---计算机网络概述
  • 视频动作识别模型-C3D
  • 线程池项目代码细节5(解决linux死锁问题)
  • 关系型数据库——GaussDB的简单学习
  • 《投资-43》- 自然=》生物=》人类社会=》商业=》金融=》股市=》投资的共同逻辑:生存竞争与进化论
  • 前端实现查询数据【导出】功能
  • 自制扫地机器人(二) Arduino 机器人避障设计——东方仙盟
  • A股大盘数据-20250901 分析
  • 设计模式:代理模式(Proxy Pattern)
  • HOW - 前端团队组长提升(沟通篇)
  • kubectl-etcd
  • RSA的CTF题目环境和做题复现第1集
  • nacos微服务介绍及环境搭建
  • linux 函数 kthread_should_stop
  • 「Unity3D」RectTransform的AnchorMin与Max直接定位到Rect四角,使得Left-Top-Right-Bottom都为0