您的 Javascript 是单线程的 - 始终是单线程的,而且它也不是先发制人的。因此发生了一个事件,一些 Javascript 开始运行以响应该事件,并且没有其他 Javascript 可以运行,直到该事件处理程序通过从被调用以启动事件处理程序的回调中返回控制权返回给解释器。
此时,另一个事件可以触发并运行另一个事件处理程序,并且在该事件处理程序返回系统之前,没有其他 Javascript 可以运行。
这适用于 node.js 运行的所有 Javascript。
啊,但并非您在 node.js 中所做的每一件事实际上都是 Javascript。假设您有两个简单的计时器:
console.log("A");
setTimeout(function() {
console.log("B");
fs.readFile("smallfile.txt", function(err, data) {
console.log("C");
});
console.log("H");
}, 1000);
console.log("D");
setTimeout(function() {
console.log("E");
fs.readFile("bigfile.txt", function(err, data) {
console.log("F");
});
console.log("I");
}, 1000);
console.log("G");
这将登录控制台
A
D
G
B
H
E
I
C
F
让我们讨论一下它为什么会这样做。
A 很明显,它是第一条语句。
然后,代码调用setTimeout(),这是非阻塞和异步的。因此,它所做的只是在未来的某个时间安排一个计时器,然后立即返回。 setTimeout() 回调在 1000 毫秒内不会准备好被调用,因此现在不会被调用。
所以,接下来在setTimeout() 调用返回后,我们执行console.log("D");
然后,它执行下一个setTimeout(),它现在再次安排一个 1000 毫秒的计时器并立即返回。
然后,它执行console.log("G");。
此时,它将控制权交还给 JS 解释器,暂时无事可做。
然后,大约 1000 毫秒后,第一个 setTimeout() 计时器在事件队列中粘贴了一个计时器事件,并且由于 JS 解释器没有运行任何其他 Javascript,它会抓取该事件并运行与该事件关联的 JS 回调。这会立即记录console.log("B");。
然后,它调用fs.readFile("smallfile.txt", ...)。这又是非阻塞和异步的,所以它所做的只是告诉底层本机代码 fs 模块实现从该文件中读取所有数据,然后立即返回。然后它运行console.log("H");。至此,这个事件处理程序就完成了,并将控制权返回给解释器。
同时,第二个setTimeout() 事件已准备就绪,因此 JS 解释器从事件队列中提取该事件并调用与其关联的回调。
运行console.log("E");,然后调用 fs.readFile("bigfile.txt", ...). This is again non-blocking and asynchronous so all it does is tell the underlying native code fs module implementation to go read all the data from this file and then returns immediately. It then runsconsole.log("I");`。至此,这个事件处理程序就完成了,并将控制权返回给解释器。
一段时间后,fs.readFile("smallfile.txt", ...) 完成并将一个事件插入到事件队列中。由于 JS 解释器无事可做,它会获取该事件并运行与之关联的回调,我们会看到 console.log("C") 的结果。然后完成并将控制权返回给解释器。
一段时间后,fs.readFile("bigfile.txt", ...) 完成并将一个事件插入到事件队列中。由于 JS 解释器无事可做,它会获取该事件并运行与之关联的回调,我们会看到 console.log("F") 的结果。然后完成并将控制权返回给解释器。
此时一切都完成了。
希望您能看到异步操作(在本机代码中实现可以与使用其他 CPU 内核的 Javascript 执行并行运行)并通过将事件放入事件队列中与 Javascript 执行同步。当 JS 解释器运行完事件处理程序后,它可以在事件队列中查找下一个要运行的事件,或者如果现在没有,则进入睡眠状态,直到下一个事件插入事件队列。
因此,虽然本机代码实现可以与 Javascript 并行运行,但仍然只有一个 Javascript 执行线程一次运行,并且另一个线程无法运行,直到前一个线程通过从任何返回将控制权返回给解释器事件处理程序回调开始执行。
在事件循环内部,实际上有一堆不同类型的队列用于不同类型的事件,它们相对于彼此有一些不同的优先级,所以它比我描述的要复杂一些在这里,但核心概念是一个事件驱动系统,它一次只运行与一个事件相关联的 Javascript(单线程)和一些操作的本地代码实现,这些操作可以与 Javascript 执行并行运行,这些操作与 Javascript 的单线程同步通过事件队列执行。