【JavaScript】这一次,彻底搞懂 JS 异步及其演进历程 ~(一)

简介: 【JavaScript】这一次,彻底搞懂 JS 异步及其演进历程 ~(一)

JavaScript 是一种单线程语言,这意味着在任何给定的实例中,JavaScript 的引擎(在托管环境中运行,如标准 Web 浏览器)一次只能执行一条语句或一行代码。在浏览器中加载 JavaScript 文件时,JavaScript 引擎会从上到下处理文件中的每一行。

虽然单线程语言简化了代码编写,因为你不必担心并发问题,但这也意味着你无法在不阻塞主线程的情况下执行网络访问等耗时操作。

  • 想象一下从 API 请求一些数据。根据情况,服务器可能需要一些时间来处理请求,同时阻塞主线程使网页无响应。而使用异步 JavaScript(例如回调、promises 和 async/await),你可以在不阻塞主线程的情况下执行耗时的网络请求。
  • 当该线程在执行某些耗时操作时(比如网络请求、文件读取等),如果这个操作是同步的,那么主线程会被完全阻塞,无法响应任何其他的用户交互事件,导致整个页面失去响应。这就是所谓的“UI 阻塞”。

为了避免这种情况的发生,JavaScript 引入了异步编程的概念。异步编程的本质是在主线程发起一个耗时操作时,不必等待其完成,而是立即返回给主线程并继续执行其他代码。当操作完成后,又将相应的结果通知到主线程。

JavaScript 的引擎在浏览器上运行,但它并不是孤立运行的。Web 浏览器结合了 JavaScript 引擎和其他附加功能(Web API),在 JavaScript 中对其进行了标记。这些 Web API(setTimeout、setInterval 等)允许 JavaScript 异步运行,允许正常的同步函数在异步任务完成时继续运行。

具体来说,异步编程使用回调函数PromiseGeneratorasync/await 等方式来实现,在执行异步操作时,主线程将异步任务委托给浏览器的底层引擎,通过 Event Loop 机制实现对异步操作的检测和处理,使得主线程能够及时响应用户的交互事件,提高了 JavaScript 的性能和用户体验。

因此,JavaScript 之所以需要异步编程,是为了提供更好的用户体验及改善性能。接下来我们就去回顾一下异步 JavaScript 的演进历程。

一、什么是异步?

1、调用堆栈(Call Stack)

调用堆栈是JavaScript中的一个结构,用来暂时保存函数调用的列表。一旦你的JavaScript应用程序开始执行,就会在调用栈的底部创建并添加一个全局执行环境。因为JavaScript是单线程的,这个页面上的所有东西都是由一个单线程执行的。这个执行线程开始一行一行地遍历你的代码。

因为JavaScript一次只能执行一件事,所以它使用调用栈来跟踪已经被调用的函数、已经被调用但还没有完成的函数等的执行环境。当一个函数被调用时,一个新的执行上下文被创建,对该函数的引用被添加到调用栈的顶部,在那里开始执行。当函数的执行完成后,就会从堆栈中移除,JavaScript的引擎就会开始执行顶部的下一个函数。

就像这样:

微信图片_20230615160819.png它被称为调用堆栈,因为它使用了数据结构中堆栈的概念,遵循后进先出(LIFO)原则。它总是会先处理堆栈顶部的调用。例如,如果你在桌子上有一堆盘子,想添加更多的盘子,你就把它们添加到顶部,如果你要从这堆盘子中拿一个或多个盘子,你就从顶部拿,而不是从底部。最后添加到这堆盘子里的盘子将是第一个被拿走的。

这与调用栈的情况相同。栈顶的函数总是首先被执行。在执行函数并将其添加到调用栈时,如果JavaScript遇到一个使用Web API方法的异步函数,它不会将其添加到调用栈中,而是将其发送到Web API容器中。

2、事件队列(Event Queue)

如果JavaScript遇到一个使用Web API方法的异步函数,它会将其发送到Web API容器中。随后,每当所需的事件发生时,该方法就会从 Web API容器 转移到事件队列中。例如,setTimeout中的回调在指定时间过后被添加到队列中。

与调用堆栈不同,事件队列遵循 FIFO(先进先出)原则,这意味着调用是按照它们被添加到队列的顺序处理的。

3、事件循环(Event Loop)

事件循环是一个无限期运行的循环,作为调用栈和事件队列之间的连接。事件循环反复检查调用栈,并将子任务从事件队列转移到调用栈。在事件循环开始从队列中转移子任务之前,调用堆栈必须是空的,这表明所有程序的常规(同步)功能已经被执行。

如果调用堆栈是空的,添加到事件队列中的第一个子任务(最老的那个)将从队列中移除,其相关的函数将被添加到调用堆栈中,并以该子任务为参数执行。

就像这样:

image.png

相关文章
|
3天前
|
前端开发 JavaScript 数据处理
在JavaScript中,异步函数是指什么
【5月更文挑战第9天】JavaScript中的异步函数用于处理非立即完成的操作,如定时器、网络请求等。它们可通过回调函数、Promise或async/await来实现。示例展示了如何使用async/await模拟网络请求:定义异步函数fetchData返回Promise,在另一异步函数processData中使用await等待结果并处理。当fetchData的Promise解析时,data变量接收结果并继续执行后续代码。注意,调用异步函数不会阻塞执行,而是会在适当时间点继续。
10 0
|
10天前
|
JSON JavaScript 前端开发
使用JavaScript和XLSX.js将数据导出为Excel文件
使用JavaScript和XLSX.js将数据导出为Excel文件
21 0
|
12天前
|
JavaScript 前端开发 开发工具
【JavaScript 技术专栏】Node.js 基础与实战
【4月更文挑战第30天】本文介绍了Node.js的基础及应用,包括事件驱动的非阻塞I/O、单线程模型和模块系统。内容涵盖Node.js的安装配置、核心模块(如http、fs、path)及实战应用,如Web服务器、文件操作和实时通信。文章还讨论了Node.js的优劣势、与其他技术的结合,并通过案例分析展示项目实施流程。总结来说,Node.js是高效后端开发工具,适合构建高并发应用,其广阔的应用前景值得开发者探索。
|
13天前
|
JSON JavaScript 前端开发
深入探讨javascript的流程控制与分支结构,以及js的函数
深入探讨javascript的流程控制与分支结构,以及js的函数
|
13天前
|
JavaScript 大数据 开发者
Node.js的异步I/O模型与事件循环:深度解析
【4月更文挑战第29天】本文深入解析Node.js的异步I/O模型和事件循环机制。Node.js采用单线程与异步I/O,遇到I/O操作时立即返回并继续执行,结果存入回调函数队列。事件循环不断检查并处理I/O事件,通过回调函数通知结果,实现非阻塞和高并发。这种事件驱动编程模型简化了编程,使开发者更专注业务逻辑,为高并发场景提供高效解决方案。
|
19天前
|
JavaScript 前端开发 算法
< JavaScript小技巧:如何优雅的用【一行代码 】实现Js中的常用功能 >
在开发中,采用简洁的语法和结构,遵循一致的命名规范,具有良好的代码组织和注释,能很好的提高代码的质量。可读性:易于阅读和理解。清晰的命名、简洁的语法和良好的代码结构可以使代码的意图更加明确,降低理解代码的难度,提高代码的可读性。可维护性:易于维护。当代码逻辑清晰、结构简洁时,开发者可以更快速地定位和修复bug,进行功能扩展或修改。同时,可读性高的代码也有助于后续的代码重构和优化。可扩展性:更具有扩展性和灵活性。清晰的代码结构和简洁的代码风格使得添加新功能、修改现有功能或扩展代码更加容易。
< JavaScript小技巧:如何优雅的用【一行代码 】实现Js中的常用功能 >
|
19天前
|
JavaScript 前端开发
js开发:请解释this关键字在JavaScript中的用法。
【4月更文挑战第23天】JavaScript的this关键字根据执行环境指向不同对象:全局中指向全局对象(如window),普通函数中默认指向全局对象,作为方法调用时指向调用对象;构造函数中指向新实例,箭头函数继承所在上下文的this。可通过call、apply、bind方法显式改变this指向。
8 1
|
1月前
|
Web App开发 缓存 JavaScript
|
1月前
|
JavaScript 前端开发
JS 单线程还是多线程,如何显示异步操作
JS 单线程还是多线程,如何显示异步操作
22 2
|
1月前
|
JavaScript 前端开发
JavaScript生成的随机数随机字符串JS生成的随机数随机字符串
JavaScript生成的随机数随机字符串JS生成的随机数随机字符串
16 1