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

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.xdnf.cn/news/19714.html

相关文章:

  • 【ARMv7】开篇:掌握ARMv7架构Soc开发技能
  • 二叉树核心操作知识点整理
  • More Effective C++ 条款22:考虑以操作符复合形式(op=)取代其独身形式(op)
  • JAVA后端开发——forEach 与方法引用(::)详解
  • CoreShop微信小程序商城框架开启多租户-添加一个WPF客户端以便进行上传产品信息和图片(6)
  • 在本地使用 Docker 创建一个易受攻击的云环境
  • leetcode46.全排列
  • 【LLIE专题】一种语义感知知识引导的低照度图像增强方案
  • Day23 机器学习流水线(管道/pipeline)
  • 小程序开发:懒加载只加载当前和滑动到的图片
  • 【Doris入门】Doris数据表模型:主键模型(Unique Key Model)详解
  • 【重学MySQL】九十六、MySQL SQL Mode高效配置全攻略
  • Linux 孤儿进程 (Orphan Process)
  • 【学Python自动化】 6.1 Python 模块系统学习笔记 (与 Rust 对照)
  • Linux中命令收集
  • UE5 C++ 第三方动态库的使用
  • 「任天堂物语」08 任天堂的山寨时代
  • 递归进阶之全排列、组合
  • JS箭头函数
  • 数字铁流:2025.9.3国庆大阅兵系统架构解析
  • 贪心算法解决固定长度区间覆盖问题:最少区间数计算
  • OpenCV 实战:图像模板匹配与旋转处理实现教程
  • G156HAN04.0 宽温域高亮工业屏技术白皮书
  • Spring Ioc —— 集合类型的依赖注入
  • Next.js渲染模式:SSR、SSG与ISR揭秘
  • 第六章:健壮Go应用:工程实践与生产就绪之测试
  • 旧实例数据库损坏sqlserver启动失败解决办法
  • Java PDF转多种图片格式:技术实践与性能优化
  • CS25FTFR010 1225 0.01R/10mR有哪些优势-华年商城
  • 联邦学习论文分享:Federated Learning via Synthetic Data