什么时候JavaScript同步?

Bri*_*ian 186 javascript jquery

我一直认为JavaScript始终是异步的.但是,我了解到有些情况并非如此(即DOM操作).有什么关于它何时同步以及什么时候它将是异步的?jQuery是否会影响这一点?

cle*_*tus 258

JavaScript始终是同步和单线程的.如果您在页面上执行JavaScript代码块,则当前不会执行该页面上的其他JavaScript.

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

JavaScript定时器使用相同类型的回调进行操作.

将JavaScript描述为异步可能会产生误导.更准确地说,JavaScript是同步的,并且具有各种回调机制的单线程.

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

  • 对不起,我不太明白这句话"代码将停止执​​行,直到调用返回(成功或出错)".你能详细说明吗?如果你还说"它不会中断正在运行的任何其他代码",那么该陈述怎么能成立呢?您是否只在第一个声明中讨论回调代码?请赐教. (30认同)
  • @cletus语句"代码将在调用返回之前停止执行"需要更正,因为执行不会停止.代码执行可以继续.否则,这意味着呼叫是同步的. (23认同)
  • 这个答案令人难以置信的误导和混乱.请参阅CMS'或Faraz Ahmad的回答. (7认同)
  • Nettuts有一个非常擅长解释异步基础知识的教程:http://net.tutsplus.com/tutorials/javascript-ajax/event-based-programming-what-async-has-over-sync/ (2认同)
  • 很遗憾这个错误且具有误导性的答案有近 200 票赞成。我尝试进行编辑,但被拒绝,因为它“偏离了帖子的初衷”。“代码将停止执​​行,直到调用返回”。这是错误的。该答案应编辑或删除。 (2认同)

小智 193

JavaScript是单线程的,并且具有同步执行模型.单线程意味着一次执行一个命令.同步意味着一次一个,即一行代码正在执行,以便代码出现.所以在JavaScript中,有一件事情一次发生.

执行上下文

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

例如

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


function xyz()
{
   abc()
   console.log('xyz');
}
var one = 1;
xyz();
Run Code Online (Sandbox Code Playgroud)

在上面的代码中,将创建一个全局执行上下文,并在此上下文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');
Run Code Online (Sandbox Code Playgroud)

<html>
    <head>

    </head>
    <body>

        <script src="program.js"></script>
    </body>
</html>
Run Code Online (Sandbox Code Playgroud)

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

waitfunction() context will be popped after this line
global context will be emptied after this line
click event handler...
Run Code Online (Sandbox Code Playgroud)

JavaScript引擎正如执行上下文部分中所解释的那样同步运行代码,浏览器异步地将事物放入事件队列中.因此,需要很长时间才能完成的功能可以中断事件处理.事件之类的浏览器中发生的事情都是通过JavaScript以这种方式处理的,如果有一个应该运行的监听器,引擎将在执行堆栈为空时运行它.并且事件按照它们发生的顺序进行处理,因此异步部分是关于引擎外部发生的事情,即当外部事件发生时引擎应该做什么.

所以JavaScript始终是同步的.

  • 这个答案很清楚,应该得到更多的赞成. (15认同)
  • 当然是我读过的Javascript异步行为的最佳解释. (6认同)
  • 完美的解释! (2认同)

CMS*_*CMS 98

JavaScript是单线程的,并且始终处理正常的同步代码流执行.

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

我建议你看一下下面的文章:

该文章将帮助您了解JavaScript的单线程特性以及定时器如何在内部工作以及异步JavaScript执行的工作方式.

async http://ejohn.org/files/427px-Timers.png


Yeh*_*rtz 15

对于真正了解 JS 工作原理的人来说,这个问题可能看起来很奇怪,但是大多数使用 JS 的人没有如此深刻的洞察力(并且不一定需要它),对他们来说这是一个相当令人困惑的点,我会试着从那个角度回答。

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

主要的混淆点来自这样一个事实,即您的浏览器能够告诉 JS 随时执行更多代码(类似于如何从控制台在页面上执行更多 JS 代码)。例如,JS 具有回调函数,其目的是允许 JS 异步执行,以便 JS 的其他部分可以在等待已执行的 JS 函数(即GET调用)返回答案时运行,JS 将继续运行,直到浏览器在那时有一个答案,事件循环(浏览器)将执行调用回调函数的 JS 代码。

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

我希望这足够清楚,可以对某人有所帮助。


tri*_*cot 5

定义

术语“异步”的含义可能略有不同,导致这里的答案看似相互矛盾,但实际上并非如此。维基百科上的异步有这样的定义:

在计算机编程中,异步是指独立于主程序流程的事件的发生以及处理此类事件的方式。这些可能是“外部”事件,例如信号的到达,或由程序发起的与程序执行同时发生的动作,程序不会阻塞以等待结果。

非 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];
}
Run Code Online (Sandbox Code Playgroud)

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

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

异步示例

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

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

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

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

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

还有更多的例子,但所有这些功能都是由宿主环境提供的,而不是由核心 EcmaScript 提供的。使用核心 EcmaScript,您可以使用Promise.resolve().

语言结构

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

DOM 事件处理

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

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

在其他情况下,DOM API 只会在适当的事件队列中分派一个事件,一旦调用堆栈被清空,JavaScript 就会接收它。

查看同步和异步事件