我一直以为JavaScript总是异步的。但是,我了解到在某些情况下不是(即DOM操作)。关于何时何时将同步以及何时异步将有很好的参考吗?jQuery会完全影响吗?
我一直以为JavaScript总是异步的。但是,我了解到在某些情况下不是(即DOM操作)。关于何时何时将同步以及何时异步将有很好的参考吗?jQuery会完全影响吗?
Answers:
JavaScript始终是同步和单线程的。如果您要在页面上执行JavaScript代码块,那么该页面上当前不会执行其他JavaScript。
JavaScript仅在可以进行Ajax调用的意义上是异步的。Ajax调用将停止执行,其他代码将能够执行,直到调用返回(成功或其他)为止,此时回调将同步运行。此时将不会再运行其他代码。它不会中断当前正在运行的任何其他代码。
JavaScript计时器与此相同类型的回调一起运行。
将JavaScript描述为异步可能会引起误解。准确地说JavaScript是同步的并且具有各种回调机制的单线程。
jQuery在Ajax调用上有一个选项可以使它们同步(带有该async: false
选项)。初学者可能会不正确地使用它,因为它允许使用一种更传统的编程模型,而该模型可能会更习惯。有问题的原因是此选项将阻止页面上的所有 JavaScript,直到完成为止,包括所有事件处理程序和计时器。
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请求的事件处理函数。重要的是要记住,仅当执行堆栈为空时才处理事件队列。
例如,请参见下面的代码,以解释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始终是同步的。
JavaScript是单线程的,并且您一直在执行常规的同步代码流执行。
JavaScript可以具有的异步行为的好例子是事件(用户交互,Ajax请求结果等)和计时器,基本上是随时可能发生的动作。
我建议您看一下以下文章:
该文章将帮助您了解JavaScript的单线程性质,计时器在内部的工作方式以及异步JavaScript执行的工作方式。
对于真正了解JS的工作原理的人来说,这个问题似乎有些错,但是大多数使用JS的人没有这么深的洞察力(并不一定需要它),对于他们来说,这是一个相当混乱的观点,我会尝试从这个角度回答。
JS在执行代码方面是同步的。每行仅在完成之前在该行之后运行,如果该行在完成之后调用一个函数,则...
造成混淆的主要原因是您的浏览器能够告诉JS随时执行更多代码(与您如何从控制台执行页面上执行更多JS代码类似)。举例来说,JS具有回调函数,其目的是允许JS异步进行行为,以便在等待已执行的JS函数(即GET
调用)返回一个答案的同时运行JS的其他部分,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节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 Job Queue中Promise.resolve()
。
EcmaScript的提供了几种语言结构,支持异步模式,比如yield
,async
,await
。但是请不要误会:外部事件不会中断 JavaScript代码。的“中断”的是yield
和await
似乎提供仅仅是一个控制,从函数调用返回后来就恢复其执行上下文的预定义的方式,或者通过JS代码(在的情况下yield
),或在事件队列(在的情况下await
)。
当JavaScript代码访问DOM API时,在某些情况下,这可能会使DOM API触发一个或多个同步通知。并且,如果您的代码中有一个事件处理程序正在侦听该事件处理程序,那么它将被调用。
这可能是抢先式并发,但事实并非如此:一旦事件处理程序返回,DOM API最终也会返回,并且原始JavaScript代码将继续。
在其他情况下,DOM API只会在适当的事件队列中调度一个事件,并且在清空调用栈后,JavaScript会对其进行提取。
查看同步和异步事件
在所有情况下都是同步的。
使用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!