异步 Javascript
🎉 本文内容概述:
- JavaScript 中的异步是什么?
- JavaScript 代码在底层是如何执行的?
- Web API 简介
setTimeout及其工作原理 - 事件循环、调用栈和回调队列的基础知识。
“异步”一词通常指的是两个或多个事件并非同时发生。这意味着可以在等待任务 B 完成的同时执行任务 A。让我通过一个例子来说明它的含义:
🍔 理解异步任务的一个场景:
假设你去了一家汉堡王餐厅,点了一个芝士汉堡。那天餐厅很忙,所以店员记下了你的订单,并告诉你需要一些时间处理,完成后会通知你。这时,你看到一台饮料机,在汉堡还在处理的时候,你买了一瓶可乐。这是一个简单的异步事件示例:在你等待汉堡处理期间,你做了另一件事——买可乐,而不是等到拿到汉堡后再去买可乐。
JavaScript 中的异步性
请看下面的代码:
console.log("start");
const data = getDataAndWait(); // it could take any amount of time
console.log(data);
console.log("end");
想象一下,如果有一个函数getDataAndWait()从远程源获取数据并返回,那么第 3 行之后的代码只有在该getDataAndWait函数返回结果后才会执行,从而阻止了后续代码的执行。这是因为 JavaScript 实际上是一种同步语言。
🤷♂️同步……什么???
JavaScript 是一种同步(每行代码按顺序执行)且单线程的语言(一次只能执行一个命令)。尽管它本质上是同步的,但我们仍然需要异步执行一些任务,例如等待 API 服务器返回数据或等待定时器结束,同时又不希望在等待期间阻塞其他代码的执行。这就是为什么我们需要找到一种方法,在同步语言中使用异步机制。
✅ 明确我们的最终目标
- 我们希望能够执行一些需要花费一些时间才能完成的任务,例如从 API 获取数据。
- 我们希望在等待耗时较长的任务响应的同时,逐行执行剩余的代码。
- 当这项耗时的任务完成后,我们希望能够知道该任务及其功能已经完成。
我之前说过我们需要一种方法在 JavaScript 中使用异步操作。但是 JavaScript 本身是同步的,对吧?那我们该怎么做呢?答案是我们不需要这样做,因为这就是 Web API 的用武之地。
🌈 Web API 和浏览器的强大功能
你现在使用的浏览器功能非常强大,它可以执行许多 JavaScript 无法完成的任务。它拥有许多强大的工具,例如计时器、本地存储、IndexedDB、获取客户端地理位置、与 DOM 元素交互、从互联网获取数据等等。例如,这个函数setTimeout实际上是一个 Web API,而不是 JavaScript 语言本身的一部分。这console是另一个 Web API 的例子,是不是很令人惊讶?
浏览器会像console这样公开 Web API,任何全局对象都可以访问它。
解决方案:
console.log("start");
setTimeout(function cb(){
console.log("Hello")
}, 1000);
console.log("end");
让我们逐行深入分析一下代码的执行过程。
代码底层执行方式简述
JavaScript 中的所有操作都在执行上下文中发生。每当程序运行时,都会在调用栈中创建一个全局执行上下文;当程序执行完毕后,该全局执行上下文会从调用栈中弹出。
- 当执行第一行代码时,由于你知道这些参数
console并非setTimeoutJavaScript 的一部分,而是浏览器自身的功能,因此它console.log()会使用console浏览器的 API 并将字符串记录start到控制台窗口中。
- 现在在第 3 行,
setTimeout()将使用定时器功能,激活 1000 毫秒的时间并注册该cb()功能。
- 现在控制权转移到下一行代码,并将日志输出
end到控制台窗口。由于没有剩余代码,程序执行完毕,全局执行上下文从调用堆栈中弹出。同步 JavaScript 代码执行完毕。但是等等,那个函数呢?cb因为 1000 毫秒的计时器结束后,该cb函数需要执行,但它并不会立即执行。
事件循环和回调队列
定时器到期后,函数cb会被移入回调队列,回调队列是一个存放等待被压入调用栈的代码的队列。但是,这个函数何时才能被压入回调队列呢?这需要一个持续循环的机制来检查调用栈是否为空。如果回调队列中有待执行的代码,它会迅速将其压入调用栈,然后执行该代码。这个机制被称为事件循环。
- 因此,该
cb语句被执行,并将该单词记录hello在浏览器控制台中。
//This is the output of the code
start
end
hello
看来我们已经达到了预期目的。该cb函数会在一段时间后执行,而不会阻塞其余代码的执行。但这种方法也存在一些缺点:
- 主要任务
cb是在浏览器控制台窗口中登录hello,但这个功能可以在回调函数中实现。由于示例非常简短,这听起来似乎没什么大问题。但在实际应用中,异步代码经常会出现回调函数嵌套在另一个回调函数中的情况,如此循环往复。这就是所谓的“回调地狱”。 - 将一个函数传递给另一个函数,仅仅为了过一段时间再执行它,可能会感觉有点奇怪。
👋 这篇文章就到这里啦,不想让大家觉得无聊,所以下一篇文章我们会探讨另一种使用 Promise 编写异步代码的方法。如果有什么想讨论的,可以在评论区留言,或者直接在 Twitter 上联系我@AyushCodes




