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"。

结果

相关推荐
清灵xmf2 分钟前
深入解析 JavaScript 事件委托
前端·javascript·html·事件委托
小妖别跑32 分钟前
PDA(程序派生地址,Program Derived Address),为什么有这个地址,而不是直接指定地址
前端·智能合约
2301_796982141 小时前
网页打开时,下载的文件text/html/重定向类型有什么作用?
前端·html
重生之我在20年代敲代码1 小时前
HTML讲解(二)head部分
前端·笔记·html·web app
天下无贼!1 小时前
2024年最新版TypeScript学习笔记——泛型、接口、枚举、自定义类型等知识点
前端·javascript·vue.js·笔记·学习·typescript·html
小白小白从不日白2 小时前
react 高阶组件
前端·javascript·react.js
程序员大金2 小时前
基于SpringBoot+Vue+MySQL的智能物流管理系统
java·javascript·vue.js·spring boot·后端·mysql·mybatis
Mingyueyixi2 小时前
Flutter Spacer引发的The ParentDataWidget Expanded(flex: 1) 惨案
前端·flutter
Rverdoser3 小时前
unocss 一直热更新打印[vite] hot updated: /__uno.css
前端·css