在我的印象中,JavaScript总是异步的。然而,我了解到在某些情况下它不是(即DOM操作)。关于什么时候是同步的,什么时候是异步的,有没有好的参考?jQuery会影响这一点吗?
JavaScript是单线程的,并且您一直在进行正常的同步代码流执行。
JavaScript可以具有的异步行为的好例子是事件(用户交互、Ajax请求结果等)和计时器,基本上是随时可能发生的操作。
我建议你看看下面这篇文章:
JavaScript定时器的工作原理
这篇文章将帮助您理解JavaScript的单线程本质,以及计时器的内部工作原理,以及异步JavaScript执行的工作原理。
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();
In the above code a global execution context will be created and in this context var one will be stored and its value will be 1... when the xyz() invocation is called then a new execution context will be created and if we had defined any variable in xyz function those variables would be stored in the execution context of xyz(). In the xyz function we invoke abc() and then the abc() execution context is created and put on the execution stack... Now when abc() finishes its context is popped from stack, then the xyz() context is popped from stack and then global context will be popped...
现在谈谈异步回调;异步意味着一次有多个。
就像执行堆栈一样,这里有事件队列。当我们想要得到JavaScript引擎中某个事件的通知时,我们可以监听该事件,该事件被放置在队列中。例如Ajax请求事件或HTTP请求事件。
Whenever the execution stack is empty, like shown in above code example, the JavaScript engine periodically looks at the event queue and sees if there is any event to be notified about. For example in the queue there were two events, an ajax request and a HTTP request. It also looks to see if there is a function which needs to be run on that event trigger... So the JavaScript engine is notified about the event and knows the respective function to execute on that event... So the JavaScript engine invokes the handler function, in the example case, e.g. AjaxHandler() will be invoked and like always when a function is invoked its execution context is placed on the execution context and now the function execution finishes and the event ajax request is also removed from the event queue... When AjaxHandler() finishes the execution stack is empty so the engine again looks at the event queue and runs the event handler function of HTTP request which was next in queue. It is important to remember that the event queue is processed only when execution stack is empty.
例如,下面的代码解释了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');
And
<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...
The JavaScript engine is running the code synchronously as explained in the execution context portion, the browser is asynchronously putting things in event queue. So the functions which take a very long time to complete can interrupt event handling. Things happening in a browser like events are handled this way by JavaScript, if there is a listener supposed to run, the engine will run it when the execution stack is empty. And events are processed in the order they happen, so the asynchronous part is about what is happening outside the engine i.e. what should the engine do when those outside events happen.
JavaScript总是同步的。
对于那些真正理解JS工作原理的人来说,这个问题似乎是不可能的,然而大多数使用JS的人没有这么深刻的洞察力(也不一定需要它),对他们来说这是一个相当令人困惑的问题,我将试着从这个角度来回答。
JS的代码执行方式是同步的。每一行只在该行完成之前运行,如果该行在该行完成之后调用函数,等等…
The main point of confusion arises from the fact that your browser is able to tell JS to execute more code at anytime (similar to how you can execute more JS code on a page from the console). As an example JS has Callback functions who's purpose is to allow JS to BEHAVE asynchronously so further parts of JS can run while waiting for a JS function that has been executed (I.E. a GET call) to return back an answer, JS will continue to run until the browser has an answer at that point the event loop (browser) will execute the JS code that calls the callback function.
因为事件循环(浏览器)可以在任何时候输入更多要执行的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(): the agent (e.g. browser) will put an event in an event queue when the timeout has expired. The monitoring of the time and the placing of the event in the queue happens by non-JavaScript code, and so you could imagine this happens in parallel with the potential execution of some JavaScript code. But the callback provided to setTimeout can only execute when the currently executing JavaScript code has ran to completion and the appropriate event queue is being read. fetch(): the agent will use OS functions to perform an HTTP request and monitor for any incoming response. Again, this non-JavaScript task may run in parallel with some JavaScript code that is still executing. But the promise resolution procedure, that will resolve the promise returned by fetch(), can only execute when the currently executing JavaScript has ran to completion. requestAnimationFrame(): the browser's rendering engine (non-JavaScript) will place an event in the JavaScript queue when it is ready to perform a paint operation. When JavaScript event is processed the callback function is executed. queueMicrotask(): immediately places an event in the microtask queue. The callback will be executed when the call stack is empty and that event is consumed.
还有更多的例子,但所有这些函数都是由宿主环境提供的,而不是由核心EcmaScript提供的。在核心EcmaScript中,您可以使用Promise.resolve()同步地将事件放置在Promise作业队列中。
语言结构
EcmaScript提供了几种语言结构来支持异步模式,例如yield、async、await。但请不要搞错:任何JavaScript代码都不会被外部事件中断。yield和await提供的“中断”似乎只是一种受控的、预定义的方法,用于从函数调用中返回,并在稍后通过JS代码(yield的情况下)或事件队列(await的情况下)恢复其执行上下文。
DOM事件处理
当JavaScript代码访问DOM API时,在某些情况下,这可能会使DOM API触发一个或多个同步通知。如果你的代码有一个事件处理程序监听它,它就会被调用。
这可能会被认为是先发制人的并发,但它不是:它是JavaScript代码发起API调用,从而控制API可以做一些事情,但这就像一个函数调用:一旦你的事件处理程序返回(s), DOM API最终也将返回,原始的JavaScript代码将继续在API调用之后。
在其他情况下,DOM API只在适当的事件队列中分派事件,而JavaScript将在调用堆栈清空后拾取它。
请参见同步和异步事件
“在我的印象中,JavaScript一直是 异步”
可以以同步方式或异步方式使用JavaScript。事实上JavaScript有很好的异步支持。例如,我可能有需要数据库请求的代码。然后,在等待请求完成的同时,我可以运行其他代码,而不依赖于该请求。这种异步编码是由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的帮助。例如:
如何知道一个函数是异步的?