JavaScript是一种同步(synchronous)且单线程的语言,那为什么又变成异步(asynchronous)了?
首先,异步JavaScript是一种允许代码并发运行而不阻塞其他代码执行的编程模式。相较于同步代码,它独立于其它代码去执行,从而可以提高Web应用程序的性能和响应速度。
其次,从用处上来讲,异步代码也特别有用,比如,我们代码经常需要与外部资源(例如,服务器和数据库)交互,这可能会导致延迟并减慢代码的执行速度。通过使用异步技术,开发人员可以避免这些延迟,并允许其他代码在等待资源可用时继续执行。
接下来我们从如何实现和如何执行两个方面来掌握异步JS。
01. 使用回调
实现异步JavaScript的一种常见技术就是使用回调。回调是作为参数传递给另一个函数的函数,并在该函数完成后执行。
举个例子,代码如下:
这段代码,3秒后会在控制台打印结果,也就是说3秒延迟后会执行回调函数。同时,这3秒期间允许其他代码继续执行。
02. 使用Promise
实现异步JavaScript另一种技术是使用Promise。Promise是一个对象,它表示异步操作的最终成功或失败,并提供处理该操作结果的机制。
举个例子,代码如下:
这段代码,我们使用构建函数创建了一个Promise,同时使用定时器,设置了3秒后成功返回Promise值,Promise通过then方法接收该返回值,同时,在此期间,允许其他代码继续执行。
03. 使用async/await
异步JavaScript还可以通过使用async/await语法来实现,这种方式提供了一种更简洁,更可读的异步代码编写方式。
举个例子,代码如下:
在这段代码中,我们使用async定义了一个函数,并使用构造函数创建了一个Promise,同样设置3秒后成功返回Promise值,然后,我们使用关键字await等待Promise返回 ,并将结果存储在变量result中。执行期间,允许函数之外的其他代码继续执行。
编写JavaScript异步代码时,了解JavaScript运行时如何处理和执行任务至关重要。这就需要你充分理解调用堆栈、事件循环、Web API、回调队列和微任务队列等概念。
你也许已经看出来了,这些概念其实说的就是要掌握和理解事件循环的执行机制。
关于JavaScript事件循环,我画了一张图,如下:
接下来对着这张图,我逐一讲一下这些概念。
01. 调用堆栈
调用堆栈是JavaScript用于管理函数调用的数据结构。它以后进先出 (LIFO) 为基础工作,这意味着最近添加的函数将首先执行。当一个函数被调用时,它被添加到堆栈的顶部。当函数返回时,它被从堆栈中删除。
关于堆栈的出入栈规则,我画了张图来理解。
图如下所示:
接下来我举个例子,帮大家详细理解一下具体代码的执行过程。
代码如下:
这段代码中,当bar函数被调用时,它被添加到调用堆栈的顶部。然后该bar函数调用该foo函数,该函数将添加到调用堆栈的顶部。当foo函数返回时,它会从堆栈中删除,然后是函数bar。
02. 事件循环
事件循环是JavaScript用于管理异步任务的机制。它会不断的检查任务队列,以查看是否有任何任务等待执行。如果有,它将任务添加到调用堆栈中。
同样,我们举个例子来讲一下
代码如下:
在这个例子中,console.log('start')和console.log('end')语句被添加到调用堆栈并首先执行。setTimeout然后调用该函数,这是一个异步任务。该setTimeout功能已添加到Web API等待下一步往任务队列中推待执行的回调内容。
03. Web API
Web API是浏览器提供的一组API,允许JavaScript与浏览器环境进行交互。这些API包括setTimeout、setInterval和fetch函数。
当调用 Web API中的函数时,它会被添加到 Web APIs中。Web APIs管理任务并在完成时将其添加到任务队列。
04. 回调队列
回调队列是一种存储异步任务回调的数据结构。当异步任务完成时,其回调被添加到回调队列中。
队列(正向)的出入队规则如下:
上图很容易看懂,符合先进先出原则。下面我们举一个实例来看一下。
代码如下:
在这个实例中,setTimeout函数和Promise都是异步任务。当setTimeout函数完成时,其回调被添加到回调队列中。当Promise函数完成时,其回调将添加到微任务队列中。
05. 微任务队列
微任务队列与回调队列类似,但它用于微任务。微任务是当前任务完成后立即执行的函数。
同样,我举个例子来讲一下。
代码如下:
在这个例子中,console.log('end')语句被添加到调用堆栈并首先执行。Promise然后调用该函数,这是一个微任务。该Promise函数的回调被添加到微任务队列中,并在当前任务完成后立即执行。
任务分:同步任务(宏微任务)和异步任务(宏微任务)
什么是事件循环?
执行完宏任务,执行宏任务的微任务
执行另一个宏任务,执行另一个宏任务的微任务
01. 先来看一张流程图
从上图看出,事件循环就是不断执行宏任务及宏任务中的微任务的过程。
02. 再来看一个例子
这段代码作为宏任务,进入主线程。执行过程如下
第一步,先遇到setTimeout,那么将其回调函数注册后分发到另一个宏任务
第二步,接下来遇到了Promise,new Promise立即执行,输出promise,then函数分发到当前宏任务的微任务
第三步,遇到console.log(),立即执行,输出console
第四步,执行当前宏任务的微任务,输出then
第五步,执行另一个宏任务,输出setTimeout
了解调用堆栈、事件循环、Web API、回调队列和微任务队列对于编写高效且响应迅速的 JavaScript 代码至关重要。