event loop

前言

JavaScript 是一种单线程的编程语言,其执行机制依赖于事件循环。事件循环(Event Loop)是 JavaScript 引擎的一部分,负责监控调用栈(Call Stack)和任务队列(Task Queue),检查是否有任务需要执行。当调用栈为空时,事件循环会从任务队列中取出一个任务并放入调用栈执行,这个过程不断重复,形成了循环。

进程与线程

在深入了解 JavaScript 的事件循环之前,我们需要先理解进程(Process)和线程(Thread)的基本概念。

进程 是一个程序在操作系统中执行的实例。每个进程都有自己独立的内存空间、系统资源和一个主线程。操作系统通过进程管理系统资源的分配。

线程 是进程内的一个执行单元,也是 CPU 调度的基本单位。一个进程可以包含多个线程,这些线程共享进程的内存空间和资源,但每个线程有自己的栈、寄存器和程序计数器。

进程和线程的区别

  • 独立性:进程之间是独立的,而线程共享进程的资源。

  • 内存空间:进程有独立的内存空间,线程共享进程的内存空间。

  • 资源开销:创建和销毁进程的开销较大,而创建和销毁线程的开销较小。

  • 通信方式:进程间通信需要借助操作系统提供的机制(如管道、消息队列等),线程间通信可以通过共享内存直接进行。

JavaScript 是单线程

工作流程

JavaScript 是单线程语言,这意味着同一时间只能执行一个任务。单线程模型简化了编程模型,因为开发者不需要处理多线程带来的复杂问题,如资源竞争和死锁。

JavaScript 的单线程特性通过调用栈和事件循环来管理任务的执行顺序。调用栈是一个 LIFO(后进先出)的数据结构,用于存储函数调用。每当一个函数被调用时,它就会被推入调用栈顶,当函数执行完毕时,它会从调用栈顶弹出。

优点

  1. 节约性能

    • 单线程意味着不需要处理线程同步、锁机制等复杂的多线程操作,避免了多线程编程中的潜在竞态条件和死锁问题。
    • 在一些情况下,单线程的管理能力更高效,因为不需要额外的线程切换和上下文切换开销。
  2. 简化编程模型

    • JavaScript 的单线程模型简化了并发编程的复杂性,使得开发者可以更专注于业务逻辑的实现,而不用过多关注线程安全、同步等问题。
  3. 避免资源竞争

    • 单线程执行避免了多线程中的资源竞争问题,如共享内存的并发读写问题,从而减少了一些潜在的 Bug 和调试难度。

同步与异步代码

同步代码 是按顺序逐行执行的代码。当一行代码在执行时,后续的代码必须等待其完成。这种方式简单但可能导致阻塞,尤其是在执行 I/O 操作或复杂计算时。

异步代码 允许代码在等待某些操作完成时,继续执行其他任务。异步操作通常通过回调函数、Promise 或 async/await 实现,避免了阻塞主线程。

同步代码示例

js 复制代码
function syncOperation() {
  console.log('Start of operation');
  // 模拟一个耗时1秒的操作
  const start = Date.now();
  while (Date.now() - start < 1000) {}
  console.log('End of operation');
}

console.log('Before sync operation');
syncOperation();
console.log('After sync operation');


// Before sync operation
// Start of operation
// End of operation
// After sync operation

异步代码示例

js 复制代码
function asyncOperation(callback) {
  console.log('Start of async operation');
  setTimeout(() => {
    console.log('End of async operation');
    callback();
  }, 1000);
}

console.log('Before async operation');
asyncOperation(() => {
  console.log('Async operation completed');
});
console.log('After async operation');


// Before async operation
// Start of async operation
// After async operation
// End of async operation
// Async operation completed

宏任务与微任务

在 JavaScript 的事件循环中,任务可以分为两类:宏任务(Macro Task)和微任务(Micro Task)。

宏任务

  • setTimeout

  • setInterval

  • setImmediate

  • I/O 操作

  • UI 渲染

微任务

  • Promise.then

  • Promise.catch

  • Promise.finally

  • process.nextTick

  • MutationObserver

事件循环执行顺序

事件循环的执行顺序是理解 JavaScript 异步行为的关键。以下是事件循环的详细执行步骤:

  1. 执行同步代码:所有同步代码都会在主线程上依次执行,直至调用栈为空。

  2. 同步代码执行完毕,检测是否有异步需要执行:当调用栈为空时,事件循环会检查是否有异步任务需要执行。

  3. 检测到异步任务,先执行微任务:微任务队列中的所有任务会被依次执行,直到微任务队列为空。

  4. 微任务执行完毕,如果有需要就会渲染页面:在浏览器环境中,渲染引擎会在微任务执行完毕后判断是否需要更新 UI,并进行页面渲染。

  5. 执行异步宏任务,开启下一次事件循环:最后,事件循环会从宏任务队列中取出一个任务执行,然后开始下一次事件循环。

示例代码

js 复制代码
console.log("script start");

async function async1() {
  await async2();

  //await 会将后续的代码阻塞进微任务队列
  console.log("async1 end");
}
async function async2() {
  console.log("async2 end");
}
async1();
setTimeout(function () {
  console.log("setTimeout");
}, 0);
new Promise(function (resolve, reject) {
  console.log("promise");
  resolve();
})
  .then(() => {
    console.log("then1");
  })
  .then(() => {
    console.log("then2");
  });
console.log("script end");

详解

  • 同步代码部分

    • console.log("script start"); 输出 "script start"。
  • 定义异步函数 async1 和 async2

    • 这两行只是定义函数,不会立即执行它们的内容。
  • 调用 async1

    • 进入 async1,执行 await async2()
    • 进入 async2,执行 console.log("async2 end"); 输出 "async2 end"。
    • async2 执行完毕,返回一个已解决的 Promise,await 使得 async1 的后续代码被推入微任务队列。
  • 设置 setTimeout

    • setTimeout 被调用,注册一个回调函数,并将其放入宏任务队列。该函数将在所有同步和微任务执行完毕后执行。
  • Promise

    • new Promise 立即执行,输出 "promise"。
    • resolve() 被调用,then 回调被推入微任务队列。
  • 输出 "script end"

    • console.log("script end"); 输出 "script end"。

结果

相关推荐
慧一居士20 分钟前
flex 布局完整功能介绍和示例演示
前端
DoraBigHead22 分钟前
小哆啦解题记——两数失踪事件
前端·算法·面试
一斤代码6 小时前
vue3 下载图片(标签内容可转图)
前端·javascript·vue
中微子6 小时前
React Router 源码深度剖析解决面试中的深层次问题
前端·react.js
光影少年6 小时前
从前端转go开发的学习路线
前端·学习·golang
中微子6 小时前
React Router 面试指南:从基础到实战
前端·react.js·前端框架
3Katrina6 小时前
深入理解 useLayoutEffect:解决 UI "闪烁"问题的利器
前端·javascript·面试
前端_学习之路7 小时前
React--Fiber 架构
前端·react.js·架构
coderlin_7 小时前
BI布局拖拽 (1) 深入react-gird-layout源码
android·javascript·react.js
伍哥的传说8 小时前
React 实现五子棋人机对战小游戏
前端·javascript·react.js·前端框架·node.js·ecmascript·js