1. JavaScript 的单线程特性

JavaScript 是单线程的,这意味着它一次只能执行一个任务。这种设计简化了编程模型,避免了多线程环境中的复杂问题(如死锁、竞态条件等)。然而,单线程也意味着如果某个任务耗时较长,可能会阻塞后续任务的执行。

为了解决这个问题,JavaScript 引入了事件循环非阻塞 I/O 机制,使得它能够在执行同步代码的同时,处理异步任务(如定时器、网络请求、I/O 操作等)。

2. 事件循环(Event Loop)

事件循环是 JavaScript 实现并发的核心机制。它允许 JavaScript 在执行同步代码的同时,处理异步任务。事件循环的工作流程如下:

2.1 调用栈(Call Stack)

  • 调用栈用于跟踪函数的执行。每当一个函数被调用,它会被推入调用栈;当函数执行完毕,它会从调用栈中弹出。

  • JavaScript 是单线程的,因此调用栈一次只能执行一个任务。

2.2 任务队列(Task Queue)

  • 异步任务(如 setTimeoutPromisefetch 等)完成后,会将对应的回调函数放入任务队列中。

  • 任务队列分为两种:

    • 宏任务队列(MacroTask Queue):包含 setTimeoutsetInterval、I/O 操作等。

    • 微任务队列(MicroTask Queue):包含 Promise 的回调、MutationObserver 等。

2.3 事件循环的工作流程

  1. 事件循环会不断检查调用栈是否为空。

  2. 当调用栈为空时,事件循环会从任务队列中取出一个任务(回调函数)并推入调用栈执行。

  3. 在执行宏任务之前,事件循环会先清空微任务队列中的所有任务。

3. 异步任务的执行顺序

JavaScript 中的异步任务执行顺序遵循以下规则:

  1. 同步代码优先执行:所有同步代码会立即执行,直到调用栈为空。

  2. 微任务优先于宏任务:每次调用栈清空后,事件循环会先处理所有微任务,然后再处理宏任务。

  3. 宏任务按顺序执行:每次事件循环只会处理一个宏任务,处理完后会再次检查微任务队列。

 

 

4. 示例代码

以下代码展示了事件循环的执行顺序:

 1Promise.resolve().then(() => {

输出结果

解释

  1. 同步代码 console.log("Start") 和 console.log("End") 立即执行。

  2. Promise 的微任务优先于 setTimeout 的宏任务执行。

  3. 最后执行 setTimeout 的宏任务。

5. 常见的异步任务类型

5.1 宏任务(MacroTask)

  • setTimeoutsetInterval

  • I/O 操作(如文件读写、网络请求)

  • UI 渲染

5.2 微任务(MicroTask)

  • Promise 的 thencatchfinally

  • MutationObserver

  • queueMicrotask

6. 事件循环的实际应用

理解事件循环的机制对于编写高效的异步代码至关重要。以下是一些实际应用场景:

6.1 避免阻塞主线程

通过将耗时任务(如网络请求、文件读写)放入异步任务队列,可以避免阻塞主线程,确保页面的流畅性。

6.2 优化任务调度

利用微任务优先执行的特性,可以确保高优先级的任务(如状态更新)能够及时处理。

6.3 处理复杂异步逻辑

通过 Promise 和 async/await,可以更清晰地表达复杂的异步逻辑,避免回调地狱(Callback Hell)。

总结

JavaScript 的并发模型基于事件循环,通过调用栈、任务队列(宏任务和微任务)来管理任务的执行顺序。尽管 JavaScript 是单线程的,但事件循环和非阻塞 I/O 机制使得它能够高效地处理并发任务。

个人笔记记录 2021 ~ 2025