JavaScript 什么时候同步?

IT技术 javascript jquery
2021-01-20 20:01:19

我一直认为 JavaScript 总是异步的。但是,我了解到有些情况并非如此(即 DOM 操作)。是否有关于何时同步和何时异步的良好参考?jQuery 会影响这个吗?

6个回答

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

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

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

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

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

抱歉,我不太明白这句话“代码将停止执​​行,直到调用返回(成功或错误)”。你能不能详细说明一下。当您还说“它不会中断任何其他正在运行的代码”时,这种说法怎么会是真的?你是不是只在第一条语句中谈论回调代码?请赐教。
2021-03-18 20:01:19
这个答案令人难以置信的误导和混乱。请参阅 CMS 或 Faraz Ahmad 的回答。
2021-03-19 20:01:19
@cletus 语句“代码将停止执​​行,直到调用返回”需要更正,因为执行不会停止。代码执行可以继续。否则,这将意味着调用是同步的。
2021-03-22 20:01:19
我也不明白那个说法。
2021-03-25 20:01:19
Nettuts 有一个非常擅长解释异步基础知识的教程:net.tutsplus.com/tutorials/javascript-ajax/...
2021-04-01 20:01:19

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() 上下文从中弹出堆栈然后将弹出全局上下文...

现在关于异步回调;异步意味着一次多个。

就像执行堆栈一样,有Event Queue当我们想要在 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 总是同步的。

当然,这需要您阅读一些有关执行上下文堆栈的内容,只有将其添加为空和事件 que 才让我终于觉得我确定性地理解了 java 脚本执行。更糟糕的是,我觉得它只需要阅读一页,但我几乎找不到任何地方。那为什么没人直接说呢?是他们不知道还是什么?但我觉得如果 js 教程有这个,它可以为我节省很多时间。>:|
2021-03-20 20:01:19
这个答案很清楚,应该得到更多的赞成。
2021-03-22 20:01:19
对执行上下文和队列的很好的解释。
2021-03-25 20:01:19
当然,我读过的对 Javascript 异步行为的最佳解释。
2021-03-26 20:01:19
完美的解释!
2021-03-30 20:01:19

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

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

我建议您查看以下文章:

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

异步

接受的答案误导我们在这种情况下可以做些什么吗?/
2021-03-15 20:01:19

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

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

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

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

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

定义

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

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

非 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 节作业和作业队列中说

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

异步示例

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

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

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

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

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

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

语言结构

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

DOM 事件处理

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

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

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

查看同步和异步事件