我一直对JavaScript始终是异步的印象。但是,我了解到在某些情况下不是这样(即DOM操作)。关于何时何时将同步以及何时异步将有很好的参考吗? jQuery会完全影响吗?

评论

始终是ajax除外。

接受的答案是错误的,并且会误导您,请进行检查。

观看youtube.com/watch?v=8aGhZQkoFbQ有助于了解事件循环,以及有关同步和异步的堆栈,Web API和任务队列的工作方式也很有用

@ defau1t没错,JavaScript始终是同步的,当ajax调用完成时,回调最终在队列中结束,这是Java脚本同步性质的一个例外。

#1 楼

JavaScript始终是同步的并且是单线程的。如果要在页面上执行JavaScript代码块,则该页面上当前将不执行其他JavaScript。

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

JavaScript计时器与此类回调一起运行。

将JavaScript描述为异步可能令人误解。准确地说JavaScript是同步的且具有各种回调机制的单线程。

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

评论


抱歉,我不太理解该语句“在调用返回(成功或错误)之前,代码将停止执​​行”。你能详细说明一下吗?当您还说“它不会中断正在运行的任何其他代码”时,该语句如何成立?您是否仅在第一条语句中谈论回调代码?请赐教。

–克里希纳
2012年12月31日17:52



Nettuts的教程非常擅长在这里解释异步的基础:net.tutsplus.com/tutorials/javascript-ajax/…

–RobW
13年4月27日在4:30

@cletus语句“代码将停止执​​行,直到调用返回”需要更正,因为执行不会停止。代码执行可以继续。否则,这意味着呼叫是同步的。

– HS。
2013年9月16日13:22

我也不明白那句话。

–嫁妆
13年11月22日在15:29

这个答案令人难以置信的误导和混乱。请改为查看CMS'或Faraz Ahmad的答案。

–iono
16年7月15日在7:59

#2 楼

JavaScript是单线程的,并具有同步执行模型。单线程意味着一次正在执行一个命令。同步意味着一次一次,即一次执行一行代码以使代码出现。因此,在JavaScript中,一次只发生一件事。

执行上下文

JavaScript引擎与浏览器中的其他引擎进行交互。
在JavaScript执行堆栈中是底部的全局上下文,然后当我们调用函数时,JavaScript引擎会为各个函数创建新的执行上下文。当被调用函数退出时,其执行上下文从堆栈中弹出,然后弹出下一个执行上下文,依此类推...

例如

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请求的事件处理函数。重要的是要记住,只有在执行堆栈为空时才处理事件队列。例如,请参见下面的代码,其中解释了Java引擎对执行堆栈和事件队列的处理。

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始终是同步的。

评论


这个答案很明确,应该得到更多支持。

– ranu
2015年11月18日,下午5:48

当然,我已经阅读了有关Javascript异步行为的最佳解释。

–查尔斯·贾梅特(Charles Jaimet)
16 Dec 30 '16:14

关于执行上下文和队列的很好的解释。

–Divyanshu Maithani
17年2月15日在15:08

当然,这需要您阅读一些有关执行上下文堆栈的信息,只有添加它为空和事件que才使我最终感觉像我对确定性的Java脚本执行有所了解。更糟糕的是,我感觉它只需要阅读一页书,却几乎找不到任何地方。那么,为什么没人说呢?他们不知道还是什么?但是我觉得如果一个js教程有这个功能,可以节省很多时间。 >:|

–元帅
17年9月8日在16:18



完美的解释!

– Petya Naumova
17年11月28日在10:03

#3 楼

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

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

我建议您看一下以下文章:


JavaScript计时器的工作原理

该文章将帮助您了解JavaScript的单线程性质,以及计时器在内部如何工作以及异步JavaScript如何执行。



评论


被接受的答案会误导我们在这种情况下可以采取某些措施?/

–苏拉杰(Suraj Jain)
18-10-27在9:40

#4 楼

对于真正了解JS的工作原理的人来说,这个问题似乎有些错,但是大多数使用JS的人没有这么深的洞察力(并不一定需要它),对于他们来说,这是一个相当混乱的观点,我会请尝试从这个角度回答。

JS在执行代码方面是同步的。每行仅在完成之前在该行之后运行,并且如果该行在完成之后调用一个函数等...

最主要的混淆点在于您的浏览器能够分辨JS可以随时执行更多代码(类似于从控制台执行页面上执行更多JS代码的方式)。举例来说,JS具有回调函数,其目的是允许JS异步进行行为,以便在等待已执行的JS函数(即GET调用)返回答案后,可以运行JS的其他部分,JS将继续运行直到浏览器给出答案为止,事件循环(浏览器)将执行调用回调函数的JS代码。

由于事件循环(浏览器)可以输入更多要在任何位置执行的JS从这个意义上讲,JS是异步的(导致浏览器输入JS代码的主要因素是超时,回调和事件)

我希望这很清楚,对某些人有帮助。 >

#5 楼

定义

术语“异步”可以用略有不同的含义使用,从而导致看似相互矛盾的答案,而实际上却并非如此。维基百科上的异步定义如下:在计算机编程中,异步是指独立于主程序流程以及处理此类事件的方式而发生的事件。这些可能是“外部”事件,例如信号的到来,或与程序执行同时发生的由程序引发的动作,而程序不会阻塞等待结果。


non -JavaScript代码可以将此类“外部”事件排队到一些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节Jobs和Jobs Queues中说:仅当没有正在运行的执行上下文并且执行上下文堆栈为空时,才可以开始执行作业。


异步的示例

正如其他人已经写过的那样,在JavaScript中,异步在多种情况下起作用,并且总是涉及事件队列,当没有其他JavaScript代码正在执行时,只能导致JavaScript执行:


setTimeout():超时时间过后,代理(例如浏览器)会将事件放入事件队列中。时间的监视和事件在队列中的放置是通过非JavaScript代码进行的,因此您可以想象这与某些JavaScript代码的潜在执行同时发生。但是提供给setTimeout的回调仅在当前执行的JavaScript代码运行完毕并且正在读取适当的事件队列时才能执行。
fetch():代理将使用OS函数执行HTTP请求并监视任何传入响应。同样,此非JavaScript任务可以与仍在执行的某些JavaScript代码并行运行。但是,将解决fetch()返回的诺言的诺言解析过程只能在当前执行的JavaScript运行完毕后执行。
requestAnimationFrame():浏览器的呈现引擎(非JavaScript)将在JavaScript中放置一个事件准备执行绘制操作时排队。处理JavaScript事件后,将执行回调函数。
queueMicrotask():立即在微任务队列中放置一个事件。当调用堆栈为空并且使用了该事件时,将执行回调。

还有更多示例,但是所有这些功能都是由主机环境提供的,而不是由核心EcmaScript提供的。使用核心EcmaScript,您可以使用Promise.resolve()将事件同步放置在Promise作业队列中。

语言构造

EcmaScript提供了多种语言构造来支持异步模式,例如yieldasyncawait。但是请不要误会:外部事件不会中断JavaScript代码。 yieldawait似乎提供的“中断”只是一种受控的,预定义的方式,可以从函数调用返回并稍后通过JS代码(对于yield)或事件队列(在对于await)。

DOM事件处理

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

这可能是抢先式并发,但事实并非如此:一旦事件处理程序返回, ),最终DOM API也将返回,并且原始的JavaScript代码将继续。

在其他情况下,DOM API只会在适当的事件队列中调度一个事件,而JavaScript会将其提取一次调用堆栈已清空。

查看同步和异步事件

#6 楼


“给人的印象是JavaScript总是异步的”。

您可以以同步方式或异步方式使用JavaScript。实际上,JavaScript具有非常好的异步支持。例如,我可能有需要数据库请求的代码。然后,我可以在等待该请求完成的同时,运行其他代码,而不是对该请求运行dependent。 Promise,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会有很多帮助。 EG:
如何知道函数是否异步?

#7 楼

在所有情况下都是同步的。
使用Promises阻塞线程的示例:
   const test = () => new Promise((result, reject) => {
    const time = new Date().getTime() + (3 * 1000);

    console.info('Test start...');

    while (new Date().getTime() < time) {
      // Waiting...
    }

    console.info('Test finish...');
  });

  test()
    .then(() => console.info('Then'))
    .finally(() => console.info('Finally'));

  console.info('Finish!');
 

输出将是:
Test start...
Test finish...
Finish!