我对Javascript的单线程性质有疑问。
console.log("1");
setTimeout(function(){console.log("2");},3000);
console.log("3");
setTimeout(function(){console.log("4");},1000);
此代码的结果为1 3 4 2
。如您所见,4
位于2
之后,这让我想知道在单线程环境中2
之后不应该4
?如果没有,那么为什么JS知道第二个setTimeout
应该在第一个之前完成?是不是应该有两个线程同时工作以完成两个setTimeout
以通知EventLoop
?
答案 0 :(得分:26)
JavaScript(在浏览器中)不会并发运行 2 。
setTimeout
回调最多一个可以一次执行 - 因为有一个 JavaScript执行上下文或“线程”。
但是,要运行的“下一个计划超时”始终运行..下一步。 “4”在“2”回调之前运行,因为计划提前运行。超时是从同时有效地安排的(没有任何操作被阻止),但是“2”的间隔时间更长。
底层实现可能使用线程 1 - 但同一全局上下文中的JavaScript不会并发运行并保证一致所有回调之间的和原子行为。
1 或者它可能不会;这可以在select/poll
实现中没有任何线程的情况下处理。
2 在相同的上下文中:即Tab / Window,WebWorker,主机浏览器控件。例如,当WebWorkers同时运行时,它们会在不同的上下文中运行,并遵循相同的异步模型(例如,计时器使用的)。
答案 1 :(得分:11)
Javascript按顺序执行每一行。
所以你告诉js:
js writes 1
ok I'll wait 3 seconds...now what?
ok I'll write 3, by the way, the 3 seconds is not up.
ok I'll wait 1 second...
然后js等待.99999秒...并写入4
然后再等一下并写下2
答案 2 :(得分:10)
Javascript使用名为 Eventloop 的内容进行异步调用。 setTimeout被推送到EventLoop,因为它是一个回调。并且主线程继续执行。一旦主要完成,然后EventLoop将数据推送到主堆栈。 例如:
console.log("1");
setTimeout(function(){console.log("2");},0);
console.log("3");
setTimeout(function(){console.log("4");},1000);
当超时为0时,代码的输出为
1 3 2 4
因为它首先执行Main的调用,然后从Eventloop返回数据 Concurrency model and Event Loop
答案 3 :(得分:5)
setTimeout的第二个参数采用最小时间,之后将回调函数(第一个参数)推送到事件循环,该循环只是回调函数的队列。该队列用于实际开始执行。
一旦遇到第一个setTimeout,函数就会被推到外面的某个地方,并被告知在重新进入单线程世界之前等待3秒。对于第二个超时功能也是如此,但它必须等待仅1秒。这个单线程世界的入口点是回调队列。 JS Engine继续正常执行,就像完成settimeout执行一样。现在1秒到期,第二次超时的功能被推送到队列并等待执行。如果调用堆栈在那个时间点是清除的,则该函数进行处理(假设它是队列的第一个成员)并且打印“4”。现在如果此时没有经过3秒,则第一次超时的功能仍然在外面的某个地方等待。一旦3秒后,回调函数进入队列,由于调用堆栈已清除,它将执行并打印“2”。
现在,浏览器可以从操作系统访问多个线程(虽然只为JS执行提供单个线程环境)。这些setTimeout由场景后面的另一个线程处理。
飞利浦罗伯特的视频精美地解释了导致单线程javascript“异步”的队列和事件循环的概念。
答案 4 :(得分:0)
以下是步骤:
将console.log(1)添加到JS调用堆栈中。时间(〜0)
执行它。 (在控制台中打印1)-时间(〜0)
添加setTimeout(function(){console.log(“ 2”);},3000);到调用堆栈。 -时间(〜0)
将其移至事件循环并启动计时器。 -时间(3秒)
由于setTimeout是一个异步函数,它将移至事件循环。
将console.log(3)添加到JS调用堆栈中。时间(〜0)
执行它。 (在控制台中打印3)时间(〜0)
添加setTimeout(function(){console.log(“ 4”);},1000);到调用堆栈。时间(〜0)
将其移至事件循环并启动计时器。 -时间(1秒)
1秒计时器已结束,因此它将移回调用堆栈并执行。
调用堆栈执行它。 (在控制台中打印4)-时间(〜0)
3秒计时器已完成,因此它将移回调用堆栈并执行。
调用堆栈执行它。 (在控制台中打印2)-时间(〜0)
现在我们所说的同步是JS调用堆栈,它一次只能执行一件事。
我本可以将其分为20个步骤,但为了便于理解,12个就足够了。
答案 5 :(得分:0)
定义-多线程: 多线程是一种执行模型,它允许多个线程存在于进程的上下文中,以便它们独立执行但共享进程资源。 从这个意义上讲,JavaScript完全是多线程的东西:
let Ar;
Ar = []
function multiapp(ar,w,n) {
ar.clear;
for (let i = 0; i < n; i++)
setTimeout(function(){ar.push(w)},i);
}
+function fn() {
//Thread 1
setTimeout(() => multiapp(Ar,1,10),100)
//Thread 2
setTimeout(() => multiapp(Ar,2,10),100)
}()
setTimeout(function(){console.log('Ar(1) = ', Ar.filter((i) => i == 1).length,'Ar(2) = ', Ar.filter((i) => i == 2).length,' Ar = ',Ar)},2000);
//Ar(1) = 10 Ar(2) = 10 Ar = [ 1, 1, 2, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2 ]`
显然,线程1独立于线程2启动并执行自己的堆栈。两个线程共享对同一上下文对象实例Ar的访问,并以未知的方式对其进行修改。行为与Java基本相同,具有原子基元。