什么时候JavaScript是同步的?

在我的印象中,JavaScript总是异步的。然而,我了解到在某些情况下它不是(即DOM操作)。关于什么时候是同步的,什么时候是异步的,有没有好的参考?jQuery会影响这一点吗?

206627 次浏览

JavaScript是单线程的,并且您一直在进行正常的同步代码流执行。

JavaScript可以具有的异步行为的好例子是事件(用户交互、Ajax请求结果等)和计时器,基本上是随时可能发生的操作。

我建议你看看下面这篇文章:

这篇文章将帮助您理解JavaScript的单线程本质,以及计时器的内部工作原理,以及异步JavaScript执行的工作原理。

async

JavaScript始终是同步和单线程的。如果你在一个页面上执行一个JavaScript代码块,那么当前该页上没有其他JavaScript将被执行。

JavaScript只是在某种意义上是异步的,比如它可以执行Ajax调用。Ajax调用将停止执行,其他代码将能够执行,直到调用返回(成功与否),此时回调将同步运行。此时不会运行其他代码。它不会中断当前正在运行的任何其他代码。

JavaScript计时器使用同样的回调操作。

将JavaScript描述为异步的可能会引起误解。更准确地说,JavaScript是同步的、单线程的,有各种回调机制。

jQuery在Ajax调用上有一个选项使它们同步(使用async: false选项)。初学者可能会不正确地使用它,因为它允许更传统的编程模型,人们可能更习惯。问题的原因是这个选项将阻塞页面上的所有 JavaScript,直到它完成,包括所有事件处理程序和计时器。

JavaScript是单线程的,有一个同步执行模型。单线程意味着一次只执行一个命令。同步意味着一次执行一行代码,即一行代码按照代码出现的顺序执行。在JavaScript中,一次只发生一件事。

执行上下文

JavaScript引擎与浏览器中的其他引擎交互。 在JavaScript执行堆栈的底部有一个全局上下文,然后当我们调用函数时,JavaScript引擎为各自的函数创建新的执行上下文。当被调用的函数退出时,它的执行上下文从堆栈中弹出,然后下一个执行上下文弹出,以此类推……< / p >

例如

function abc()
{
console.log('abc');
}




function xyz()
{
abc()
console.log('xyz');
}
var one = 1;
xyz();

在上面的代码中,将创建一个全局执行上下文,并在此上下文中存储var one,其值为1…当调用xyz()时,将创建一个新的执行上下文,如果我们在xyz函数中定义了任何变量,这些变量将存储在xyz()的执行上下文中。在xyz函数中,我们调用abc(),然后abc()执行上下文被创建并放在执行堆栈上…现在,当abc()完成它的上下文从堆栈中弹出,然后xyz()上下文从堆栈中弹出,然后全局上下文将弹出…

现在谈谈异步回调;异步意味着一次有多个。

就像执行堆栈一样,这里有事件队列。当我们想要得到JavaScript引擎中某个事件的通知时,我们可以监听该事件,该事件被放置在队列中。例如Ajax请求事件或HTTP请求事件。

每当执行堆栈为空时(如上面的代码示例所示),JavaScript引擎就会定期查看事件队列,看看是否有任何需要通知的事件。例如,在队列中有两个事件,一个ajax请求和一个HTTP请求。它还查看是否有一个函数需要在该事件触发器上运行……因此,JavaScript引擎会收到关于事件的通知,并知道要在该事件上执行的相应函数……所以JavaScript引擎调用处理函数,在例子中,例如AjaxHandler()将被调用,就像总是当一个函数被调用时,它的执行上下文被放置在执行上下文上,现在函数执行完成,事件ajax请求也从事件队列中删除…当AjaxHandler()完成时,执行堆栈为空,因此引擎再次查看事件队列并运行HTTP请求的事件处理程序函数,该函数是队列中的下一个。重要的是要记住,只有当执行堆栈为空时才会处理事件队列。

例如,下面的代码解释了Javascript引擎的执行堆栈和事件队列处理。

function waitfunction() {
var a = 5000 + new Date().getTime();
while (new Date() < a){}
console.log('waitfunction() context will be popped after this line');
}


function clickHandler() {
console.log('click event handler...');
}


document.addEventListener('click', clickHandler);




waitfunction(); //a new context for this function is created and placed on the execution stack
console.log('global context will be popped after this line');

<html>
<head>


</head>
<body>


<script src="program.js"></script>
</body>
</html>

现在运行该网页并单击该页面,然后在控制台上查看输出。输出将是

waitfunction() context will be popped after this line
global context will be emptied after this line
click event handler...

JavaScript引擎正在同步运行代码,如执行上下文部分所述,浏览器正在异步地将内容放入事件队列中。因此,需要很长时间才能完成的函数可能会中断事件处理。浏览器中发生的事情,如事件,由JavaScript以这种方式处理,如果有一个侦听器要运行,引擎将在执行堆栈为空时运行它。事件是按照它们发生的顺序处理的,所以异步部分是关于在引擎外部发生了什么,即当这些外部事件发生时,引擎应该做什么。

JavaScript总是同步的。

对于那些真正理解JS工作原理的人来说,这个问题似乎是不可能的,然而大多数使用JS的人没有这么深刻的洞察力(也不一定需要它),对他们来说这是一个相当令人困惑的问题,我将试着从这个角度来回答。

JS的代码执行方式是同步的。每一行只在该行完成之前运行,如果该行在该行完成之后调用函数,等等…

混淆的主要原因是浏览器能够告诉JS在任何时候执行更多的代码(类似于你如何从控制台在一个页面上执行更多的JS代码)。作为一个例子,JS有回调函数,其目的是允许JS的行为异步,以便JS的进一步部分可以在等待已经执行的JS函数(即GET调用)返回答案时运行,JS将继续运行,直到浏览器得到答案,此时事件循环(浏览器)将执行调用回调函数的JS代码。

因为事件循环(浏览器)可以在任何时候输入更多要执行的JS,所以JS是异步的(导致浏览器输入JS代码的主要原因是超时、回调和事件)

我希望这足够清楚,对大家有所帮助。

定义

术语“异步”;可以用在略有不同的意义上,导致这里看起来相互矛盾的答案,而实际上不是。关于异步的维基百科有这样的定义:

在计算机编程中,异步是指独立于主程序流的事件的发生以及处理此类事件的方法。这些可能是“outside”;在程序执行的同时发生的事件,如信号的到达或由程序引起的操作,而不需要程序阻塞以等待结果。

非javascript代码可以排队这样的“outside”;一些JavaScript的事件队列。但也就到此为止了。

没有抢占

没有运行JavaScript代码的外部中断来执行脚本中的其他JavaScript代码。JavaScript片段一个接一个地执行,顺序由每个事件队列中的事件顺序以及这些队列的优先级决定。

例如,你可以绝对确定在执行下面这段代码时,没有其他JavaScript(在同一个脚本中)会执行:

let a = [1, 4, 15, 7, 2];
let sum = 0;
for (let i = 0; i < a.length; i++) {
sum += a[i];
}

换句话说,JavaScript中没有抢占。无论事件队列中有什么,这些事件的处理都必须等待,直到这段代码运行完成。EcmaScript规范在第8.4节作业和作业队列中说:

只有当没有正在运行的执行上下文且执行上下文堆栈为空时,才能启动Job的执行。

异步的例子

正如其他人已经写过的,在JavaScript中有几种情况需要异步,并且总是涉及到一个事件队列,只有当没有其他JavaScript代码执行时,才会导致JavaScript执行:

  • setTimeout():代理(例如浏览器)将在超时过期时将一个事件放入事件队列中。时间监视和事件在队列中的放置是由非JavaScript代码执行的,因此您可以想象这与一些JavaScript代码的潜在执行并行进行。但是提供给setTimeout的回调只能在当前正在执行的JavaScript代码运行完成并且正在读取相应的事件队列时执行。

  • fetch():代理将使用OS函数来执行HTTP请求并监视任何传入的响应。同样,这个非JavaScript任务可能与一些仍在执行的JavaScript代码并行运行。但是承诺解析过程(它将解析fetch()返回的承诺)只能在当前正在执行的JavaScript运行完成时执行。

  • requestAnimationFrame():浏览器的渲染引擎(非JavaScript)在准备执行绘制操作时将在JavaScript队列中放置一个事件。当JavaScript事件被处理时,回调函数被执行。

  • queueMicrotask():立即在微任务队列中放置一个事件。当调用堆栈为空并且该事件被消费时,将执行回调。

还有更多的例子,但所有这些函数都是由宿主环境提供的,而不是由核心EcmaScript提供的。在核心EcmaScript中,你可以用Promise.resolve()在承诺作业队列中同步放置一个事件。

语言结构

EcmaScript提供了一些语言结构来支持异步模式,例如yieldasyncawait。但请不要搞错:任何JavaScript代码都不会被外部事件打断了。“interruption"yieldawait似乎只是提供了一种受控的预定义方式,用于从函数调用返回并稍后恢复其执行上下文,或通过JS代码(在yield的情况下),或通过事件队列(在await的情况下)。

DOM事件处理

当JavaScript代码访问DOM API时,在某些情况下,这可能会使DOM API触发一个或多个同步通知。如果你的代码有一个事件处理程序监听它,它就会被调用。

这可能会被认为是先发制人的并发,但它不是:它是JavaScript代码发起API调用,从而控制API可以做一些事情,但这就像一个函数调用:一旦你的事件处理程序返回(s), DOM API最终也将返回,原始的JavaScript代码将继续在API调用之后。

在其他情况下,DOM API只在适当的事件队列中分派事件,而JavaScript将在调用堆栈清空后拾取它。

看到同步和异步事件

"在我的印象中,JavaScript一直是 asynchronous" < / p >

可以以同步方式或异步方式使用JavaScript。事实上JavaScript有很好的异步支持。例如,我可能有需要数据库请求的代码。然后,在等待请求完成时,我可以在该请求上运行其他代码,而不是dependent。这种异步编码是由promises、async/await等支持的。但如果你不需要一个很好的方法来处理长时间的等待,那就同步使用JS。

我们所说的“异步”是什么意思?它并不是指多线程,而是描述了一种非依赖关系。看看这个流行的回答的图片:

         A-Start ------------------------------------------ A-End
| B-Start -----------------------------------------|--- B-End
|    |      C-Start ------------------- C-End      |      |
|    |       |                           |         |      |
V    V       V                           V         V      V
1 thread->|<-A-|<--B---|<-C-|-A-|-C-|--A--|-B-|--C-->|---A---->|--B-->|

我们看到单线程应用程序可以具有异步行为。函数A中的工作并不依赖于函数B的完成,因此当函数A在函数B之前开始时,函数A可以在稍后的时间在同一线程上完成。

因此,仅仅因为JavaScript在单个线程上一次执行一个命令,并不意味着JavaScript只能用作同步语言。

“是否有一个好的参考什么时候是同步的,什么时候是异步的”;

我想知道这是否是你问题的核心。我认为你的意思是,你怎么知道你正在调用的一些代码是异步的还是同步的。也就是说,当您等待某个结果时,您的其余代码是否会运行并执行某些操作?首先检查的应该是所使用库的文档。例如,节点方法有清晰的名称,如readFileSync。如果文档不是很好,这里有很多关于SO的帮助。例如:

如何知道一个函数是异步的?< / >