帮助你很好理解js的事件循环

JavaScript 的事件循环(Event Loop)是其实现异步编程的核心机制。即使 JavaScript 是单线程的,事件循环允许它处理非阻塞 I/O 操作、定时器、网络请求等异步任务。以下是详细解释:


核心概念

  1. 调用栈(Call Stack)

    • 一个后进先出(LIFO)的结构,用于跟踪当前正在执行的函数。
    • 当函数被调用时,它会被推入栈顶;执行完毕后弹出。
  2. 堆(Heap)

    • 内存中存储对象(如变量、函数等)的区域。
  3. 任务队列(Task Queue)

    • 一个先进先出(FIFO)的队列,用于存放待处理的异步任务回调。
    • 分为两种类型:
      • 宏任务队列(MacroTask Queue):如 setTimeoutsetInterval、DOM 事件、I/O 操作等。
      • 微任务队列(MicroTask Queue):如 Promise.then()MutationObserverqueueMicrotask()

事件循环的工作流程

事件循环按以下步骤循环执行:

  1. 执行同步代码

    • 所有同步代码(调用栈中的任务)会优先执行,直到调用栈清空。
  2. 处理微任务队列

    • 每次调用栈清空后,事件循环会立即依次执行微任务队列中的所有任务,直到队列为空。
    • 微任务具有高优先级,会在下一个宏任务执行前被处理。
  3. 处理宏任务队列

    • 从宏任务队列中取出一个任务(如最早的 setTimeout 回调),推入调用栈执行。
    • 执行过程中可能产生新的微任务或宏任务。
  4. 重复循环

    • 重复步骤 1~3,直到所有队列为空。

关键规则

  1. 微任务优先于宏任务

    • 每次事件循环迭代中,微任务会全部执行完毕,才会处理下一个宏任务。
    setTimeout(() => console.log('宏任务'), 0);
    Promise.resolve().then(() => console.log('微任务'));
    // 输出顺序:微任务 → 宏任务
    
  2. 渲染(UI Update)的时机

    • 浏览器会在微任务队列清空后、下一个宏任务执行前进行页面渲染。
  3. 避免阻塞事件循环

    • 长时间运行的同步代码会阻塞事件循环,导致页面卡顿。

比喻理解

家庭版事件循环

角色设定

  • :JavaScript 主线程(单线程,只能同时做一件事)
  • 你媳妇:事件循环的监督者(负责调度任务)
  • 宏任务:你想玩的游戏(比如一局 LOL,需要较长时间)
  • 微任务:琐碎的家务(必须立即完成的小事,比如洗碗、倒垃圾)

规则细化
  1. 初始状态

    • 你坐在电脑前,游戏还没开始(事件循环启动前的初始状态)。
    • 你媳妇会不断检查任务清单(事件循环的持续运行)。
  2. 执行阶段

    • 第一步:处理「同步任务」(虽然没有直接比喻,但可以理解为):

      • 你突然想起今天要交水电费,立刻起身去缴费(同步代码立即执行,比如 console.log)。
    • 第二步:处理所有「微任务」

      • 缴费回来后,你媳妇说:“想打游戏?先做完所有家务!”
      • 你开始洗碗(微任务1)→ 洗到一半发现垃圾满了 → 顺手倒垃圾(新微任务2)→ 倒完垃圾继续洗碗 → 最终所有家务完成(清空微任务队列)。
    • 第三步:处理一个「宏任务」

      • 家务做完后,你终于能打一局 LOL(执行一个宏任务,比如 setTimeout 回调)。
    • 第四步:循环检查

      • 打完一局后,你媳妇立刻出现:“还想再玩?再检查有没有新家务!”
      • 如果这局游戏中你点了外卖(宏任务中产生新微任务,如 Promise.then),你必须先去取外卖(处理新微任务),才能开下一局。

关键细节强化
  1. 微任务的「插队」特性

    • 即使你正在倒垃圾(处理微任务2),突然发现猫没喂(新微任务3),也必须立刻喂猫,再继续倒垃圾。
    • 只要家务没做完,游戏永远不能开始
  2. 宏任务的「批处理」特性

    • 你媳妇允许你一次只打一局游戏(每次事件循环处理一个宏任务),打完必须重新检查家务。
    • 如果你同时预约了晚上8点和9点的游戏(多个 setTimeout),你媳妇会按预约时间让你一局一局玩。
  3. 不可抗拒的规则

    • 如果你在打游戏时(执行宏任务),又产生了新家务(微任务),必须在本局游戏结束后立刻处理,即使下一局游戏已经到预约时间。
    // 类比代码
    setTimeout(() => {
      console.log("打第一局游戏"); // 宏任务
      Promise.resolve().then(() => console.log("取外卖")); // 微任务
    }, 0);
    setTimeout(() => console.log("打第二局游戏"), 0);
    
    // 输出顺序:打第一局游戏 → 取外卖 → 打第二局游戏
    

比喻 vs 事件循环的对照表
比喻场景事件循环机制
你只能单线程做事JavaScript 主线程单线程执行
必须做完所有家务才能打游戏微任务队列清空后才执行下一个宏任务
打一局游戏后重新检查家务每次宏任务执行后重新处理微任务队列
做家务时产生新家务必须处理微任务可嵌套,且会持续清空队列

总结:家庭生存法则
  • 优先级:媳妇的指令(微任务) > 你的游戏(宏任务)。
  • 代价:如果家务太多(微任务无限循环),你永远打不了游戏(宏任务被阻塞)。
  • 启示:写代码时要像处理夫妻关系一样,避免微任务爆炸,才能让事件循环和谐运转!

核心总结

  1. 执行顺序优先级
    • 每次事件循环(Event Loop)的一次完整迭代中:
      同步代码 → 所有微任务 → 一个宏任务 → 所有微任务 → 下一个宏任务 → …
  2. 关键点
    • 微任务队列会在 当前宏任务结束 后、下一个宏任务开始前 被清空。
    • 如果微任务中又产生了新的微任务,这些新微任务也会 立即执行,直到队列为空。
    • 宏任务队列每次只处理一个任务,处理完后会再次检查微任务队列。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值