深入理解 JavaScript 进程、线程与事件循环

171 阅读4分钟

深入理解 JavaScript 进程、线程与事件循环

进程 & 线程

在计算机科学中,进程是指一个程序在运行过程中分配和管理资源的基本单位。而线程则是进程中更小的单位,负责执行一段指令。简单来说,进程是程序的一次执行,而线程是在进程中执行的一个单独的路径。

  • 进程:指的是 CPU 运行指令和保存上下文所需的时间。
  • 线程:是进程中更小的单位,指的是一段指令执行所需要的时间。

浏览器新开一个 Tab 页(进程)

当你在浏览器中新开一个 Tab 页时,涉及到多个线程协同工作,以保证流畅的用户体验。

  1. 渲染线程:负责将 HTML、CSS 和 JavaScript 转换为可视化页面,同时处理用户交互。
  2. HTTP 请求线程:用于处理发送和接收 HTTP 请求的线程。
  3. JavaScript 引擎线程:执行 JavaScript 代码的线程,处理与 JavaScript 相关的任务。

线程之间可以协同工作,但渲染线程和 JavaScript 引擎线程是互斥的,防止发生数据竞争和不一致的情况。

JavaScript 是单线程的

JavaScript 是一种单线程的语言,意味着它一次只能执行一个任务。这带来了一些优势

  1. 节约内存:不需要为多线程分配额外的内存,减少了内存占用。
  2. 无锁概念:由于是单线程,不存在多线程间的数据竞争,也就没有锁的概念,减少了上下文切换的时间。

异步

在 JavaScript 中,异步是一种非阻塞的执行方式,通过回调函数、Promise、Async/Await 等机制实现。

  • 宏任务(macrotask) :包括 script 代码、setTimeout、setInterval、setImmediate、I/O 操作和 UI 渲染等。
  • 微任务(microtask) :包括 Promise 的 then 方法、MutationObserver 和 process.nextTick()。

Event-Loop(浏览器角度)

浏览器的 Event-Loop 是一个重要的概念,描述了 JavaScript 的执行顺序。 它会按以下步骤执行

  1. 执行同步代码:同步代码属于宏任务,会按照顺序执行。
  2. 执行栈为空时:当执行栈为空时,检查是否有需要执行的异步任务。
  3. 执行微任务:先执行微任务队列中的任务,确保微任务在下一轮 Event-Loop 之前执行。
  4. 渲染页面:如果需要,会渲染页面以保证用户界面更新。
  5. 执行宏任务:执行宏任务队列中的任务,开始下一轮 Event-Loop。

接下来我们通过一些代码来理解事件循环(以下代码没有渲染这一步)

tips: 当v8 在碰到微任务和宏任务时,会创建一个微任务队列和一个宏任务队列,执行完同步代码后,便会按以上步骤继续执行。

console.log('start');
async function async1() {  
    await async2() 
   //await 一脚把后续代码踹到微任务队列里,它自己也是微任务。但是浏览器给await开小灶提速,它会直接执行
    console.log('saync1 end'); 
}
async function async2() {
    console.log('saync2 end');
}
async1()  //调用函数 (步骤1)
setTimeout(function () {
    console.log('setTimeout');
}, 0)
new Promise((resolve) => {
    console.log('promise');
    resolve()
})
    .then(() => {
        console.log('then1');
    })
    .then(() => {
        console.log('then2');
    })
console.log('end');
// 如此梳理:
// 代码行1:打印顺序 1 (步骤1)
//      2:入微任务队列
//      7:入微任务队列
//      10:调用函数(步骤1)
//      3:立即执行
//      8:打印顺序 2 (步骤1)
//      4:因为await的原因不再是同步代码,入微任务队列
//      11:入宏任务队列
//      14:(new一个对象是同步代码)
//      15:打印顺序 3 (步骤1)
//      16:调用函数。(then 可以执行,但它是微任务)
//      24:打印顺序 4 (步骤1)
// 步骤1就执行完了,接着步骤2,检查是否有异步任务,先把微任务队列里执行完,按照先入先出的原则,
// 接着再执行宏任务队列里的异步代码。当执行到第11行代码时,此时正在执行步骤5,
// **既是本次事件的结束,也是下次事件的开始**。这便是事件循环
// 所以打印顺序为:'start' 'saync2 end' 'promise' 'end' 'saync1 end' 'then1' 'then2' 'setTimeout'

建议搭配前一篇文章观看,效果更佳哦 juejin.cn/post/730823…

深入理解 JavaScript 的进程、线程以及事件循环机制有助于更好地编写高效、非阻塞的代码,提升 Web 应用的性能和用户体验