GXB's Blog

  • 首页
  • 学习笔记
    • C++
    • golang
    • javascript
    • python
  • 工具分享
  • 其它
Kratos
专注于用户阅读体验的响应式博客主题
  1. 首页
  2. 学习笔记
  3. javascript
  4. 正文

JavaScript 事件循环:理解异步编程的核心

2026年4月25日 67点热度 0人点赞 0条评论

为什么需要事件循环?

JavaScript 是单线程语言,意味着同一时间只能做一件事。但如果有一个任务需要等待(比如网络请求),难道要让整个页面卡住吗?

当然不会。这就是事件循环发挥作用的地方。

一句话解释事件循环

事件循环是一个不断运行的机制,它负责执行调用栈中的代码,并将异步任务的回调放入恰当的时间执行。

核心组件

┌─────────────────────────┐
│     调用栈 (Call Stack)  │  ← 同步代码在这里执行
└─────────────────────────┘
           │
           ▼
┌─────────────────────────┐
│    Web APIs / 任务队列   │  ← 异步任务等待的地方
│  (setTimeout, fetch等)   │
└─────────────────────────┘
           │
           ▼
┌─────────────────────────┐
│   回调队列 (Callback Queue)│  ← 等待进入调用栈的任务
└─────────────────────────┘

经典例子:猜猜输出顺序?

console.log('1');

setTimeout(() => {
  console.log('2');
}, 0);

Promise.resolve().then(() => {
  console.log('3');
});

console.log('4');

// 输出: 1, 4, 3, 2
// 是不是和你猜的一样?

宏任务与微任务

事件循环中有两种任务队列,优先级不同:

类型 常见API 优先级
微任务 Promise.then, async/await, MutationObserver 高
宏任务 setTimeout, setInterval, I/O, UI渲染 低

执行规则:每执行一个宏任务前,会先清空所有微任务队列。

setTimeout(() => console.log('宏任务1'), 0);
setTimeout(() => console.log('宏任务2'), 0);

Promise.resolve().then(() => {
  console.log('微任务1');
  Promise.resolve().then(() => console.log('微任务2'));
});

console.log('同步代码');

// 输出: 同步代码 → 微任务1 → 微任务2 → 宏任务1 → 宏任务2
setTimeout(() => console.log('宏任务1'), 0);
setTimeout(() => console.log('宏任务2'), 0);

Promise.resolve().then(() => {
  console.log('微任务1');
  Promise.resolve().then(() => console.log('微任务2'));
});

console.log('同步代码');

// 输出: 同步代码 → 微任务1 → 微任务2 → 宏任务1 → 宏任务2

async/await 的本质

async/await 是 Promise 的语法糖,理解它能帮助你更好地掌握事件循环:

async function demo() {
  console.log('A');
  await Promise.resolve();
  console.log('B');
}

console.log('C');
demo();
console.log('D');

// 输出: C, A, D, B

常见面试题:输出顺序

async function async1() {
  console.log('async1 start');
  await async2();
  console.log('async1 end');
}

async function async2() {
  console.log('async2');
}

console.log('script start');

setTimeout(() => {
  console.log('setTimeout');
}, 0);

async1();

new Promise((resolve) => {
  console.log('promise1');
  resolve();
}).then(() => {
  console.log('promise2');
});

console.log('script end');

// 答案:
// script start
// async1 start
// async2
// promise1
// script end
// async1 end
// promise2
// setTimeout

实用技巧:让 setTimeout 变成“同步”

// 等待指定时间(同步阻塞版本,不推荐)
function sleep(ms) {
  const start = Date.now();
  while (Date.now() - start < ms) {}
}

// 更好的异步版本
function delay(ms) {
  return new Promise(resolve => setTimeout(resolve, ms));
}

async function demo() {
  console.log('开始');
  await delay(2000);
  console.log('2秒后');
}

事件循环对性能的影响

❌ 坏:长时间阻塞调用栈

// 这会卡死页面
function heavyTask() {
  let count = 0;
  for (let i = 0; i < 1000000000; i++) {
    count++;
  }
  console.log(count);
}

✅ 好:分片处理

function processLargeArray(items, chunkSize = 100, callback) {
  let index = 0;
  
  function processChunk() {
    const end = Math.min(index + chunkSize, items.length);
    for (let i = index; i < end; i++) {
      // 处理 items[i]
    }
    index = end;
    
    if (index < items.length) {
      setTimeout(processChunk, 0);
    } else {
      callback();
    }
  }
  
  processChunk();
}

记住这张图就够了

┌─────┐     ┌─────────┐
│同步代码│ ──▶ │ 微任务队列 │ ──┐
└─────┘     └─────────┘   │
                    │
┌─────┐     ┌─────────┐   │
│宏任务│ ◀── │ 宏任务队列 │ ◀─┘
└─────┘     └─────────┘

一句话总结:先同步、再清空微任务、再取一个宏任务,重复循环。

标签: 暂无
最后更新:2026年4月25日

admin

这个人很懒,什么都没留下

点赞
< 上一篇
下一篇 >

文章评论

razz evil exclaim smile redface biggrin eek confused idea lol mad twisted rolleyes wink cool arrow neutral cry mrgreen drooling persevering
取消回复

归档

  • 2026 年 5 月
  • 2026 年 4 月

分类

  • C++
  • golang
  • javascript
  • python
  • 学习笔记
  • 工具分享

COPYRIGHT © 2026 GXB's Blog. ALL RIGHTS RESERVED.

Theme Kratos Made By Seaton Jiang

粤ICP备2024198348号-6