"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. 执行宏任务:执行下一个宏任务。

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

如下:

js 复制代码
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

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

arduino 复制代码
stard
async2 end
promise
end
then1
then2
setTimeout

结论

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

相关推荐
旧味清欢|1 分钟前
关注分离(Separation of Concerns)在前端开发中的实践演进:从 XMLHttpRequest 到 Fetch API
javascript·http·es6
热爱编程的小曾19 分钟前
sqli-labs靶场 less 8
前端·数据库·less
gongzemin30 分钟前
React 和 Vue3 在事件传递的区别
前端·vue.js·react.js
Apifox43 分钟前
如何在 Apifox 中通过 Runner 运行包含云端数据库连接配置的测试场景
前端·后端·ci/cd
-代号95271 小时前
【JavaScript】十四、轮播图
javascript·css·css3
树上有只程序猿1 小时前
后端思维之高并发处理方案
前端
庸俗今天不摸鱼2 小时前
【万字总结】前端全方位性能优化指南(十)——自适应优化系统、遗传算法调参、Service Worker智能降级方案
前端·性能优化·webassembly
QTX187302 小时前
JavaScript 中的原型链与继承
开发语言·javascript·原型模式
黄毛火烧雪下2 小时前
React Context API 用于在组件树中共享全局状态
前端·javascript·react.js
Apifox2 小时前
如何在 Apifox 中通过 CLI 运行包含云端数据库连接配置的测试场景
前端·后端·程序员