我正在思考它,这就是我想出来的:
假设我们有这样的代码:
console.clear();
console.log("a");
setTimeout(function(){console.log("b");},1000);
console.log("c");
setTimeout(function(){console.log("d");},0);
请求进来,JS引擎开始逐步执行上面的代码。前两个呼叫是同步呼叫。但是当谈到setTimeout
方法时,它就变成了异步执行。但是JS立即从它返回并继续执行,称为Non-Blocking
或Async
。它继续致力于其他等。
执行结果如下:
a c d b
所以基本上第二个setTimeout
首先完成,它的回调函数比第一个更早执行,这是有道理的。
我们在这里讨论单线程应用程序。 JS引擎继续执行此操作,除非它完成第一个请求,否则它不会进入第二个请求。但好处是它不会等待setTimeout
之类的阻塞操作来解决,所以它会更快,因为它接受新的传入请求。
但我的问题出现在以下几个方面:
#1:如果我们讨论的是单线程应用程序,那么当JS引擎接受更多请求并执行它们时,什么机制会处理setTimeouts
?单个线程如何继续处理其他请求?当其他请求继续进入并执行时,对setTimeout
有效。
#2:如果这些setTimeout
函数在幕后执行,而有更多请求进入和执行,那么在幕后执行异步执行的是什么?我们谈论的这个问题叫做EventLoop
?
#3:但是不应该将整个方法放在EventLoop
中,以便整个事情被执行并调用回调方法?这是我在谈论回调函数时所理解的:
function downloadFile(filePath, callback)
{
blah.downloadFile(filePath);
callback();
}
但是在这种情况下,JS引擎如何知道它是否是异步函数,以便它可以将回调放在C#中的EventLoop? Perhaps something like the
async`关键字或某种表示方法JS的属性中引擎将采用异步方法,应相应处理。
#4:但article表示与我猜测事情可能起作用的方式完全相反:
Event Loop是一个回调函数队列。当一个异步 函数执行后,回调函数被推入队列。该 JavaScript引擎在开始之前不会开始处理事件循环 执行异步函数后的代码。
#5:这里有这张图片可能会有所帮助,但图片中的第一个解释就是说问题4中提到的完全相同:
所以我的问题是如何澄清上面列出的项目?
答案 0 :(得分:81)
1:如果我们谈论的是单线程应用程序,那么当JS引擎接受更多请求并执行它们时,什么处理setTimeouts?是不是单个线程会继续处理其他请求?然后谁将继续处理setTimeout,而其他请求继续执行并执行。
节点进程中只有一个线程可以实际执行程序的JavaScript。但是,在节点本身内,实际上有几个线程处理事件循环机制的操作,这包括一个IO线程池和一些其他线程。关键是这些线程的数量与处理的并发连接数不对应,就像在每个连接的并发连接模型中一样。
现在关于“执行setTimeouts”,当你调用setTimeout
时,所有节点都基本上更新了将来要执行的函数的数据结构。它基本上有一堆需要做的东西和事件循环的每个“滴答”,它选择一个,将它从队列中删除,然后运行它。
要理解的关键是节点依赖于操作系统来完成大部分繁重的工作。因此,传入的网络请求实际上是由操作系统本身跟踪的,当节点准备好处理它时,它只是使用系统调用向操作系统请求数据准备好处理的网络请求。 IO“工作”节点的大部分功能都是“Hey OS,网络连接是否已准备好读取数据?”或者“嘿OS,我的任何优秀文件系统调用都准备好了数据?”。基于其内部算法和事件循环引擎设计,节点将选择一个“滴答”的JavaScript来执行,运行它,然后重复该过程。这就是事件循环的含义。 Node基本上始终在确定“我应该运行的下一小部分JavaScript是什么?”,然后运行它。操作系统已完成的IO以及通过调用setTimeout
或process.nextTick
在JavaScript中排队的内容。
2:如果这些setTimeout会在更多请求进入和执行的情况下在幕后执行,那么在幕后进行异步执行的事情就是我们所说的EventLoop吗?
没有JavaScript在幕后执行。程序中的所有JavaScript都在前面和中间运行,一次一个。幕后发生的事情是操作系统处理IO和节点等待准备就绪,节点管理其等待执行的javascript队列。
3:JS引擎如何知道它是否是异步函数,以便它可以将它放在EventLoop中?
节点核心中有一组固定的异步函数,因为它们进行系统调用,节点知道这些是因为它们必须调用OS或C ++。基本上所有网络和文件系统IO以及子进程交互都是异步的,并且JavaScript可以通过调用节点核心库提供的异步函数之一来让节点异步运行某些东西。即使您使用定义它自己的API的npm包,为了产生事件循环,最终npm包的代码将调用节点核心的异步函数之一,并且当节点知道节拍完成并且它可以启动事件时循环算法再次。
4 Event Loop是一个回调函数队列。执行异步函数时,回调函数将被推入队列。在执行异步函数之后的代码之前,JavaScript引擎不会开始处理事件循环。
是的,这是事实,但这是误导。关键是正常模式是:
//Let's say this code is running in tick 1
fs.readFile("/home/barney/colors.txt", function (error, data) {
//The code inside this callback function will absolutely NOT run in tick 1
//It will run in some tick >= 2
});
//This code will absolutely also run in tick 1
//HOWEVER, typically there's not much else to do here,
//so at some point soon after queueing up some async IO, this tick
//will have nothing useful to do so it will just end because the IO result
//is necessary before anything useful can be done
所以是的,你可以完全阻止事件循环,只需同时计算所有内存中的斐波那契数字,但是这样会完全冻结你的程序。这是合作并发。 JavaScript的每个滴答必须在一段合理的时间内产生事件循环,否则整体架构将失败。
答案 1 :(得分:56)
菲利普罗伯茨有一个很棒的视频教程,它以最简单和概念的方式解释了javascript事件循环。每个javascript开发人员都应该看看。
以下是Youtube上的视频link。
答案 2 :(得分:10)
不要认为主机进程是单线程的,它们不是。什么是单线程是执行javascript代码的主机进程的一部分。
除了background workers,但这些情况复杂化了......
所以,你所有的js代码都在同一个线程中运行,并且你不可能让你的js代码的两个不同部分同时运行(所以,你得到的并发nigthmare就不会管理)。
正在执行的js代码是主机进程从事件循环中拾取的最后一个代码。 在你的代码中,你基本上可以做两件事:运行同步指令,并安排在将来发生某些事件时要执行的函数。
以下是您的示例代码的心理表示(请注意:我只是,我不知道浏览器实现细节!):
console.clear(); //exec sync
console.log("a"); //exec sync
setTimeout( //schedule inAWhile to be executed at now +1 s
function inAWhile(){
console.log("b");
},1000);
console.log("c"); //exec sync
setTimeout(
function justNow(){ //schedule justNow to be executed just now
console.log("d");
},0);
当您的代码正在运行时,主机进程中的另一个线程会跟踪正在发生的所有系统事件(UI上的点击,读取的文件,收到的网络数据包等)。
当您的代码完成后,它将从事件循环中删除,主机进程将返回检查它,以查看是否有更多代码要运行。事件循环包含两个事件处理程序:一个现在要执行(justNow函数),另一个在一秒内(inAWhile函数)。
主机进程现在尝试匹配发生的所有事件,以查看是否有为其注册的处理程序。 它发现justNow正在等待的事件已经发生,所以它开始运行它的代码。当justNow函数退出时,它会再次检查事件循环,为事件上的处理程序进行搜索。假设1 s已经过去,它运行inAWhile函数,依此类推......
答案 3 :(得分:1)
事件循环有一项简单的工作 - 来监视调用堆栈、回调队列和微任务队列。如果调用堆栈为空,则事件循环将从微任务队列中获取第一个事件,然后从回调队列中获取,并将其推送到调用堆栈,后者有效地运行它。这样的迭代在事件循环中称为滴答。
正如大多数开发人员所知,Javascript 是单线程的,这意味着 javascript 中的两个语句不能并行执行,这是正确的。逐行执行,这意味着每个 javascript 语句都是同步和阻塞的。但是有一种方法可以异步运行您的代码,如果您使用 setTimeout() 函数,浏览器提供的 Web API,它可以确保您的代码在指定时间(以毫秒为单位)后执行。
示例:
console.log("Start");
setTimeout(function cbT(){
console.log("Set time out");
},5000);
fetch("http://developerstips.com/").then(function cbF(){
console.log("Call back from developerstips");
});
// Millions of line code
// for example it will take 10000 millisecond to execute
console.log("End");
setTimeout 将回调函数作为第一个参数,以毫秒为单位的时间作为第二个参数。 在浏览器控制台执行上述语句后会打印
Start
End
Call back from developerstips
Set time out
注意:您的异步代码在所有同步代码执行完毕后运行。
理解代码是如何一行一行执行的
JS 引擎执行第一行并在控制台打印“Start”
在第2行看到了名为cbT的setTimeout函数,JS引擎将cbT函数推送到callBack队列中。
此后指针会直接跳转到第7行,在那里它会看到promise和JS引擎将cbF函数推送到微任务队列。
然后它会执行数百万行代码并结束它会打印“End”
主线程执行结束后,事件循环会先检查微任务队列,然后回调队列。在我们的例子中,它从微任务队列中取出 cbF 函数并将其推入调用堆栈,然后它将从回调队列中选取 cbT 函数并推入调用堆栈。
答案 4 :(得分:0)
JavaScript 是高级单线程语言、解释型语言。这意味着它需要一个将 JS 代码转换为机器代码的解释器。解释器的意思是引擎。用于 chrome 的 V8 引擎和用于 safari 的 webkit。每个引擎都包含内存、调用堆栈、事件循环、计时器、Web API、事件等。
事件循环的概念非常简单。有一个无限循环,JavaScript 引擎等待任务,执行它们然后休眠,等待更多任务
任务已设置 - 引擎处理它们 - 然后等待更多任务(同时休眠并消耗接近零的 CPU)。可能会发生在引擎繁忙时任务到来,然后将其排队的情况。任务形成一个队列,即所谓的“宏任务队列”
微任务完全来自我们的代码。它们通常由承诺创建: .then/catch/finally 处理程序的执行成为一个微任务。微任务也在 await 的“掩护下”使用,因为它是承诺处理的另一种形式。在每个宏任务之后,引擎会立即执行微任务队列中的所有任务,然后再运行任何其他宏任务或渲染或其他任何内容。