“JavaScript的事件循环机制揭秘:解密异步编程的核心”

摘要:本文将介绍进程、线程以及JavaScript的单线程特性。我们将详细探讨它们的定义、特点和作用,并重点关注JavaScript中的事件循环机制。


引言

计算机中的进程和线程是操作系统中非常重要的概念,它们对于程序的执行和资源的管理起着至关重要的作用。而JavaScript作为一门广泛应用于Web开发的脚本语言,有着自己独特的特性和限制。本文将以浏览器中的JavaScript执行环境为例,深入探讨进程、线程和JavaScript的单线程特性。

进程和线程

进程指的是cpu在运行指令和保存上下文所需的时间。每个进程都有自己独立的内存空间,它们之间相互隔离。而线程是进程中的更小单位,用于执行程序的指令。一个进程可以由多个线程组成,它们共享进程的资源,包括内存空间和文件等。

在浏览器中,打开一个新的标签页就会创建一个新的进程。而这个进程中又包含多个线程,用于处理不同的任务。例如,渲染线程负责将HTML、CSS和JavaScript转化为可视化的页面,HTTP请求线程负责发送网络请求和接收响应,而JavaScript引擎线程则负责执行JavaScript代码。

JavaScript的单线程特性

与其他编程语言不同,JavaScript是一门单线程的语言,即它在同一时间只能执行一个任务。这是由JavaScript的设计和用途决定的。

优点

  1. 节约内存:由于只有一个线程,JavaScript不需要为每个线程分配独立的内存空间,节约了内存资源。

  2. 无锁问题:多线程编程中常见的问题是竞态条件和死锁等锁问题。而JavaScript的单线程特性避免了这些问题,减少了上下文切换的时间。

缺点

  1. 性能限制:由于只有一个线程,JavaScript的性能受到限制。当执行长时间运行的任务时,会阻塞用户界面的响应。

为了解决这个问题,JavaScript引入了异步编程的概念。

异步编程

在JavaScript中,异步编程是通过事件循环机制来实现的。事件循环(Event Loop)是JavaScript执行环境的核心机制之一。

宏任务和微任务

在事件循环中,任务被分为宏任务和微任务:

  • 宏任务:包括script代码、setTimeout、setInterval、setImmediate、I/O操作和UI渲染等。

  • 微任务:包括Promise.then、MutationObserver和process.nextTick等。

注意:

  • async === return new Promise((resolve, reject) => {})
  • await === Promise.then()

详情见:从回调地狱到async/await:JavaScript异步执行的进化之路! – 掘金 (juejin.cn)

Event Loop的执行过程

事件循环的执行过程如下:

  1. 执行同步代码(宏任务):首先执行当前执行栈中的同步代码。注意:浏览器会给await提速,当成同步代码执行

  2. 查询异步任务:当执行栈为空时,事件循环会查询是否有需要执行的异步任务。

  3. 执行微任务:如果存在微任务队列,事件循环会执行微任务队列中的所有任务。

  4. 渲染页面:如果需要,事件循环将渲染页面,更新用户界面。

  5. 执行宏任务:执行下一个宏任务。

这个过程会不断重复,形成一个事件循环。

如下:

console.log('stard');
async function async1() {
    await async2()  // 浏览器会给await提速,当成同步代码执行
    console.log('async1 end');
}

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

async1()
setTimeout(() => {
    console.log('setTimeout');
}, 0);
new Promise((resolve) => {
    console.log('promise');
    resolve()
})
.then(() => {
    console.log('then1');
})
.then(() => {
    console.log('then2');
})
console.log('end');
  1. 首先我们执行第一个宏任务中的同步代码console.log('stard'),输出了stard

  2. 然后,定义了两个异步函数async1async2。其中,async1函数内部使用了await async2(),这会导致async2函数被等待执行。

  3. 调用async1(),开始执行async1函数。在这个过程中,会输出async2 end,然后将执行权交回给async1函数。

  4. 接着,遇到了setTimeout函数,它被放入宏任务队列中,等待执行。

  5. 然后,遇到了Promise对象的构造函数,是同步代码,它会立即执行,并输出promise。由于resolve()是同步执行的,所以then1then2也会立即被放入微任务队列。

  6. 继续执行同步代码console.log('end'),输出end

  7. 此时,当前执行栈已经空了,开始执行微任务。首先执行微任务队列中的then1,输出then1,然后执行then2,输出then2

  8. 微任务队列中的任务执行完毕,开始执行下一个宏任务。这时候,执行宏任务队列中的setTimeout,输出setTimeout

所以,最终的输出结果为:

stard
async2 end
promise
end
then1
then2
setTimeout

结论

本文介绍了进程、线程和JavaScript的单线程特性。进程指的是cpu在运行指令和保存上下文所需的时间,而线程是进程中的更小单位。JavaScript作为一门单线程语言,在同一时间只能执行一个任务。为了解决单线程的性能限制,JavaScript引入了异步编程的概念,并通过事件循环机制来实现。理解这些概念对于开发高效的JavaScript应用程序至关重要。

原文链接:https://juejin.cn/post/7323408577708523554 作者:小白234

(0)
上一篇 2024年1月15日 上午10:31
下一篇 2024年1月15日 上午10:41

相关推荐

发表回复

登录后才能评论