如何掌握异步JavaScript?

JavaScript是一种同步(synchronous)且单线程的语言,那为什么又变成异步(asynchronous)了?

首先,异步JavaScript是一种允许代码并发运行而不阻塞其他代码执行的编程模式。相较于同步代码,它独立于其它代码去执行,从而可以提高Web应用程序的性能和响应速度。

其次,从用处上来讲,异步代码也特别有用,比如,我们代码经常需要与外部资源(例如,服务器和数据库)交互,这可能会导致延迟并减慢代码的执行速度。通过使用异步技术,开发人员可以避免这些延迟,并允许其他代码在等待资源可用时继续执行。

接下来我们从如何实现如何执行两个方面来掌握异步JS。

一、异步JavaScript如何实现?

01. 使用回调

实现异步JavaScript的一种常见技术就是使用回调。回调是作为参数传递给另一个函数的函数,并在该函数完成后执行。

举个例子,代码如下:

js 复制代码
setTimeout(function() {
  console.log('executed after a 3 second delay.');
}, 3000)

这段代码,3秒后会在控制台打印结果,也就是说3秒延迟后会执行回调函数。同时,这3秒期间允许其他代码继续执行。

02. 使用Promise

实现异步JavaScript另一种技术是使用PromisePromise是一个对象,它表示异步操作的最终成功或失败,并提供处理该操作结果的机制。

举个例子,代码如下:

js 复制代码
const promise = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve('Success!');
  }, 3000);
});

promise.then((result) => {
  console.log(result);
  // 输出:Success
});

这段代码,我们使用构建函数创建了一个Promise,同时使用定时器,设置了3秒后成功返回Promise值,Promise通过then方法接收该返回值,同时,在此期间,允许其他代码继续执行。

03. 使用async/await

异步JavaScript还可以通过使用async/await语法来实现,这种方式提供了一种更简洁,更可读的异步代码编写方式。

举个例子,代码如下:

js 复制代码
async function example() {
  const result = await new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve('Success!');
    }, 3000);
  });

  console.log(result);
  // 输出:Success
}

example();

在这段代码中,我们使用async定义了一个函数,并使用构造函数创建了一个Promise,同样设置3秒后成功返回Promise值,然后,我们使用关键字await等待Promise返回 ,并将结果存储在变量result中。执行期间,允许函数之外的其他代码继续执行。

二、异步JavaScript如何执行?

编写JavaScript异步代码时,了解JavaScript运行时如何处理和执行任务至关重要。这就需要你充分理解调用堆栈、事件循环、Web API、回调队列和微任务队列等概念。 你也许已经看出来了,这些概念其实说的就是要掌握和理解事件循环的执行机制。

关于JavaScript事件循环,我画了一张图,如下:

接下来对着这张图,我逐一讲一下这些概念。

01. 调用堆栈

调用堆栈是JavaScript用于管理函数调用的数据结构。它以 后进先出(LIFO) 为基础工作,这意味着最近添加的函数将首先执行。当一个函数被调用时,它被添加到堆栈的顶部。当函数返回时,它被从堆栈中删除。

关于堆栈的出入栈规则,我画了张图来理解。

图如下所示:

入栈演示图

出栈演示图

接下来我举个例子,帮大家详细理解一下具体代码的执行过程。

代码如下:

js 复制代码
function foo() {
  console.log('foo');
}

function bar() {
  console.log('bar');
  foo();
}

bar();

这段代码中,当bar函数被调用时,它被添加到调用堆栈的顶部。然后该bar函数调用该foo函数,该函数将添加到调用堆栈的顶部。当foo函数返回时,它会从堆栈中删除,然后是函数bar

02. 事件循环

事件循环是JavaScript用于管理异步任务的机制。它会不断的检查任务队列,以查看是否有任何任务等待执行。如果有,它将任务添加到调用堆栈中。

同样,我们举个例子来讲一下

代码如下:

js 复制代码
console.log('start');

setTimeout(() => {
  console.log('setTimeout');
}, 0);

console.log('end');

在这个例子中,console.log('start')console.log('end')语句被添加到调用堆栈并首先执行。setTimeout然后调用该函数,这是一个异步任务。该setTimeout功能已添加到Web API等待下一步往任务队列中推待执行的回调内容。

03. Web API

Web API是浏览器提供的一组API,允许JavaScript与浏览器环境进行交互。这些API包括setTimeoutsetIntervalfetch函数。

当调用 Web API中的函数时,它会被添加到 Web APIs中。Web APIs管理任务并在完成时将其添加到任务队列。

04. 回调队列

回调队列是一种存储异步任务回调的数据结构。当异步任务完成时,其回调被添加到回调队列中。

队列(正向)的出入队规则如下:

上图很容易看懂,符合先进先出原则。下面我们举一个实例来看一下。

代码如下:

js 复制代码
setTimeout(() => {
  console.log('setTimeout');
}, 0);

Promise.resolve().then(() => {
  console.log('Promise');
});

在这个实例中,setTimeout函数和Promise都是异步任务。当setTimeout函数完成时,其回调被添加到回调队列 中。当Promise函数完成时,其回调将添加到微任务队列中。

05. 微任务队列

微任务队列与回调队列类似,但它用于微任务。微任务是当前任务完成后立即执行的函数。

同样,我举个例子来讲一下。

代码如下:

js 复制代码
Promise.resolve().then(() => {
  console.log('Promise');
});

console.log('end');

在这个例子中,console.log('end')语句被添加到调用堆栈 并首先执行。Promise然后调用该函数,这是一个微任务。该Promise函数的回调被添加到微任务队列中,并在当前任务完成后立即执行。

三、实例分解执行过程

任务分:同步任务(宏微任务)和异步任务(宏微任务)

什么是事件循环?

执行完宏任务,执行宏任务的微任务

执行另一个宏任务,执行另一个宏任务的微任务

01. 先来看一张流程图

从上图看出,事件循环就是不断执行宏任务及宏任务中的微任务的过程。

02. 再来看一个例子

js 复制代码
new Promise(function(resolve) {
  console.log('promise');
  resolve();
}).then(function() {
  console.log('then');
})

console.log('console');

这段代码作为宏任务,进入主线程。执行过程如下

第一步, 先遇到setTimeout,那么将其回调函数注册后分发到另一个宏任务。

第二步, 接下来遇到了Promise,new Promise立即执行,输出promisethen函数分发到当前宏任务的微任务。

第三步, 遇到console.log(),立即执行,输出console

第四步, 执行当前宏任务的微任务,输出then

第五步, 执行另一个宏任务,输出setTimeout

总结

了解调用堆栈、事件循环、Web API、回调队列和微任务队列对于编写高效且响应迅速的 JavaScript 代码至关重要。

相关推荐
腾讯TNTWeb前端团队6 小时前
helux v5 发布了,像pinia一样优雅地管理你的react状态吧
前端·javascript·react.js
范文杰9 小时前
AI 时代如何更高效开发前端组件?21st.dev 给了一种答案
前端·ai编程
拉不动的猪9 小时前
刷刷题50(常见的js数据通信与渲染问题)
前端·javascript·面试
拉不动的猪9 小时前
JS多线程Webworks中的几种实战场景演示
前端·javascript·面试
FreeCultureBoy10 小时前
macOS 命令行 原生挂载 webdav 方法
前端
uhakadotcom11 小时前
Astro 框架:快速构建内容驱动型网站的利器
前端·javascript·面试
uhakadotcom11 小时前
了解Nest.js和Next.js:如何选择合适的框架
前端·javascript·面试
uhakadotcom11 小时前
React与Next.js:基础知识及应用场景
前端·面试·github
uhakadotcom11 小时前
Remix 框架:性能与易用性的完美结合
前端·javascript·面试
uhakadotcom11 小时前
Node.js 包管理器:npm vs pnpm
前端·javascript·面试