我一直认为JavaScript始终是异步的。但是,我了解到有些情况并非如此(即DOM操作)。有什么关于它何时是同步的以及它何时是异步的,是否有一个很好的参考? jQuery是否会对此产生影响?
答案 0 :(得分:232)
JavaScript始终是同步和单线程的。如果您在页面上执行JavaScript代码块,则当前不会执行该页面上的其他JavaScript。
JavaScript只是异步,因为它可以进行Ajax调用。 Ajax调用将停止执行,其他代码将能够执行,直到调用返回(成功或其他),此时回调将同步运行。此时不会运行其他代码。它不会中断当前正在运行的任何其他代码。
JavaScript定时器使用相同类型的回调进行操作。
将JavaScript描述为异步可能会产生误导。更准确地说,JavaScript是同步的,并且具有各种回调机制的单线程。
jQuery有一个Ajax调用选项,可以同步(使用async: false
选项)。初学者可能会错误地使用它,因为它允许更传统的编程模型,人们可能更习惯。它有问题的原因是该选项将阻止页面上的所有 JavaScript,直到它完成,包括所有事件处理程序和计时器。
答案 1 :(得分:169)
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始终是同步的。
答案 2 :(得分:95)
JavaScript是单线程的,并且一直处理正常的同步代码流执行。
JavaScript可以拥有的异步行为的好例子是事件(用户交互,Ajax请求结果等)和计时器,基本上是可能随时发生的行为。
我建议你看一下以下文章:
该文章将帮助您了解JavaScript的单线程特性以及定时器如何在内部工作以及异步JavaScript执行的工作方式。
答案 3 :(得分:2)
对于真正了解JS的工作原理的人来说,这个问题似乎有些错,但是大多数使用JS的人没有这么深的洞察力(并不一定需要它),这对他们来说是一个相当混乱的观点,我将尝试从这个角度回答。
JS在执行代码方面是同步的。每行仅在完成之前在该行之后运行,并且如果该行在完成之后调用函数,则该行...
主要的混淆点来自于您的浏览器能够随时告诉JS执行更多代码的事实(类似于您如何从控制台执行页面上执行更多JS代码的事实)。例如,JS具有回调函数,其目的是允许JS异步进行行为,以便在等待已执行的JS函数(即GET
调用)返回答案JS的同时运行JS的其他部分会继续运行,直到浏览器在此时给出答案为止,事件循环(浏览器)将执行调用回调函数的JS代码。
由于事件循环(浏览器)可以输入更多要在任何时候执行的JS,因此JS是异步的(导致浏览器输入JS代码的主要因素是超时,回调和事件)
我希望这很清楚,可以对某人有所帮助。
答案 4 :(得分:1)
“我一直以为JavaScript始终是 异步”
您可以以同步方式或异步方式使用JavaScript。实际上,JavaScript具有非常好的异步支持。例如,我可能有需要数据库请求的代码。然后,我可以在等待该请求完成的同时,运行其他代码,而不是对该请求运行dependent
。 Promise,async / await等都支持这种异步编码。但是,如果您不需要处理长时间等待的好方法,则只需同步使用JS。
“异步”是什么意思。嗯,这并不意味着多线程,而是描述了一种非依赖关系。从这个流行的answer中查看这张图片:
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:
答案 5 :(得分:0)
JavaScript始终是同步的并且是单线程的。只有少数函数可以使其异步运行。例如,连接到数据库,发出API请求,调用setTimeOut
函数等。
console.log("1");
function taketime(){
for(var i=0;i<1000000;i++){ if(i==1000000) console.log(i);}
}
taketime();
console.log("2");
首先打印“ 1”,主线程阻塞直到完成taketime()函数,最后打印“ 2”。
如果JS是异步的,那么输出将是
1
2
10000000
答案 6 :(得分:0)
“异步”一词的含义可能略有不同,导致此处的答案看似矛盾,而实际上却并非如此。 Wikipedia on Asynchrony的定义如下:
在计算机编程中,异步是指独立于主程序流程和处理此类事件的方式而发生的事件。这些可能是“外部”事件,例如信号的到来,或者是程序激发的与程序执行同时发生的动作,而程序不会阻塞等待结果。
非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中没有preemption。无论事件队列中有什么内容,这些事件的处理都必须等待,直到这段代码运行完毕。 EcmaScript规范在section 8.4 Jobs and 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 Job Queue中。
EcmaScript提供了几种支持异步模式的语言构造,例如yield
,async
,await
。但请不要误会:外部事件不会中断 JavaScript代码。 yield
和await
似乎提供的“中断”只是一种受控的,预定义的方法,可以从函数调用返回并稍后通过JS代码恢复其执行上下文(对于{ {1}}或事件队列(对于yield
)。
答案 7 :(得分:0)
在所有情况下都是同步的。
使用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!
答案 8 :(得分:-1)
哈哈! 演示https://jsfiddle.net/9vs1gbtq/ 对于此声明,“ JavaScript始终是同步的并且是单线程的。如果您正在页面上执行JavaScript代码块,那么该页面上当前将不会执行其他JavaScript。”
我的答案是:
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="Generator" content="EditPlus,the best editor in the World">
<title>jQuery interval plug-in</title>
<style type="text/css">
p{font-size:24px;color:red}
</style>
</head>
<body>
<p id="thread0">0</p><p id="thread1">0</p>
<p id="thread2">0</p><p id="thread3">0</p>
<p id="thread4">0</p><p id="thread5">0</p>
<p id="thread6">0</p><p id="thread7">0</p>
<p id="thread8">0</p><p id="thread9">0</p>
<!-- these are multiple choises to jquery or zepto (local or from cdns) -->
<!--
i am using these in xampp ,under windows server:
<script src="../js/zepto.min.js"></script>
<script src="../js/jquery.js"></script>
-->
<!--
<script src="https://code.jquery.com/jquery-3.4.0.min.js"></script>
<script src="https://code.jquery.com/jquery-3.4.0.js"></script>
<script src="https://code.jquery.com/jquery-3.4.0.slim.js"></script>
<script src="https://code.jquery.com/jquery-3.4.0.slim.min.js"></script>
-->
<script src="https://cdnjs.cloudflare.com/ajax/libs/zepto/1.2.0/zepto.min.js"></script>
<!-- (Zepto.js is the equivalent of jQuery,but faster ,created by genious :
thanx Thomas Fuchs and contributors!!!
) -->
<!--
<script src="https://cdnjs.cloudflare.com/ajax/libs/zepto/1.2.0/zepto.js"></script>
-->
<!--
-->
<script>
//THIS IS MY PLUGIN ON https://github.com/xpectmore/jQuery-Interval
//start plug-in
jqzp=function(){console.log('jQuery/Zepto not found: please enable one of them!');}
if (typeof($) == 'undefined'){ jqzp(); }else $( document ).ready(function(){
var _sI=[];
$.interval=function(){
if (arguments.length==3){
if (_sI[arguments[0]]== undefined){
_sI[arguments[0]]={};
}else{
clearInterval(_sI[arguments[0]].reg);
}
_sI[arguments[0]].fn=arguments[2];
_sI[arguments[0]].t=arguments[1];
_sI[arguments[0]].reg=setInterval(arguments[2],arguments[1]);
}else if (arguments.length==1){
clearInterval(_sI[arguments[0]].reg);
}else{
console.log('The number of the arguments should be 3 or 1');
console.log('$.interval("id",time_in_milliseconds,function(){do_what_you_want();})');
console.log('to stop that use: $.interval("id")');
}
}
});
//stop plug-in
</script>
<script>
$( document ).ready(function(){
var th0=0;
$.interval('th0',500,function(){
$('#thread0').text(th0);
th0++;
});
var th1=0;
$.interval('th1',1000,function(){
$('#thread1').text(th1);
th1++;
});
var th2=0;
$.interval('th2',1500,function(){
$('#thread2').text(th2);
th2++;
});
var th3=0;
$.interval('th3',500,function(){
$('#thread3').text(th3);
th3++;
});
var th4=0;
$.interval('th4',1000,function(){
$('#thread4').text(th4);
th4++;
});
var th5=0;
$.interval('th5',1500,function(){
$('#thread5').text(th5);
th5++;
});
var th6=0;
$.interval('th6',500,function(){
$('#thread6').text(th6);
th6++;
});
var th7=0;
$.interval('th7',1000,function(){
$('#thread7').text(th7);
th7++;
});
var th8=0;
$.interval('th8',1500,function(){
$('#thread8').text(th8);
th8++;
});
var th9=0;
$.interval('th9',500,function(){
$('#thread9').text(th9);
th9++;
});
});
</script>
</body>
</html>