了解 JavaScript 中的异步
定义:异步是“异步”的缩写形式。同步意味着一个接一个地执行语句,这意味着只有在前一个语句完全执行后才会执行下一个语句。而在异步调用中,下一个语句甚至无需等待前一个语句的执行即可执行。
JavaScript 是一种同步语言。这意味着默认情况下,文件中的第 10 行将在第 9 行之后运行。如果你在第 9 行声明了一个变量并在第 10 行使用它,那么如果你在第 9 行之前运行第 10 行,你可能会撞到自己的头。
然而,打破这种流动有一些好处,特别是当通过所涉及的网络进行任何类型的数据传输时,因为这需要时间。这就是我们需要引入异步性的地方——覆盖正常执行流程的能力,以节省时间。
我们刚刚听说 JavaScript 已经构建为同步的,我们最好不要对语言进行任何重大更改。此外,异步性意味着我们必须引入线程并处理死锁。 Java做到了,相信我,我们最好不要以同样的方式使 JavaScript 复杂化。
我们该怎么做呢?
答案在于 JavaScript 是如何工作的,或者更确切地说,它在哪里工作——在浏览器中。它使用这些内置函数,通常称为 Web 浏览器 API,用于控制命令流、使用调用堆栈、网络数据获取等。这些 Web 浏览器 API 通常被误认为是 JavaScript 的一部分,但实际上它们是浏览器的一部分,而 JavaScript 只使用它们。
现在这样想,我们真的不希望 JavaScript 干扰执行流程,但我们仍然希望某些代码行在预期运行之前运行。我们该怎么做呢?很简单,请在控制流程方面有发言权的其他人暂停某些代码,同时 JavaScript 可以继续处理代码,本质上是制作同步代码,即 Async。
听起来很可笑,这实际上就是 JavaScript 一直在做的事情。
我们大多数人都听说过一个名为setTimeout()方法的函数。我们错误地认为它是一个 JavaScript函数,但不幸的是,它不是。这是一个门面,一个幕后发生的奇怪事情的正面。而“幕后发生的一些奇怪的事情”是称为 Timer 的 Web 浏览器 API。这就是我们的同步 JavaScript 代码开始异步的地方。
让我们用一个示例代码来看看。
Javascript
function whoSaysGfg(){
console.log('g');
}
setTimeout(whoSaysGfg, 1000);
console.log('f');
Javascript
function whoSaysGfg(){
console.log('g');
}
setTimeout(whoSaysGfg, 0);
console.log('fg');
我们可能倾向于相信setTimeout()所做的是等待 1000 毫秒,然后执行whoSaysGfg()函数,然后继续。
在 1000 毫秒内会发生什么? JavaScript 只是坐在那里拍苍蝇吗?如果他们这样做,他们将是非常糟糕的开发人员。相信我,他们一点也不糟糕。实际发生的情况是setTimeout()是对 Timer API 的调用,计时器限制作为参数传入,在这种情况下为 1000 ms,另一个参数是回调函数,它将在计时器完成计数时运行1000 毫秒。
注意: JavaScript 本质上所做的,是将whoSaysGfg执行的控制权交给了 Web 浏览器。对于 JavaScript,该setTimeout行已经结束。它可以很好地继续下一行代码,而无需关心whoSaysGfg是否已运行。所以,JavaScript 就变成了异步。
尝试猜测这可能是什么输出?
Javascript
function whoSaysGfg(){
console.log('g');
}
setTimeout(whoSaysGfg, 0);
console.log('fg');
如果你在想'gfg',那是不正确的。如果你认为'fgg',你是对的。我们可能倾向于认为超时 0 基本上没有任何意义,并且whoSaysGfg()函数会立即被回调。
这与函数在浏览器中的调用和运行方式有关。当脚本运行并从那里调用时,函数被添加到调用堆栈中。现在,当函数的执行被推迟到浏览器时,就像setTimeout的情况一样,函数位于称为任务队列或回调队列或宏任务队列的队列中。它坐在那里,直到整个调用堆栈为空。
“事件循环”是一项服务,它不断检查调用堆栈和任务队列,并安排任务队列上的函数在调用堆栈为空时立即进入调用堆栈。
这样一来,JavaScript,一种同步语言就变成了异步的。
请注意,这不是最佳解决方案。像 promises 和 async-await 这样的解决方案已经出现,但这是理解 Async 概念的开始。