Node.js执行程序需要哪些步骤?

Node.js执行程序需要哪些步骤?,node.js,Node.js,我研究node.js已经有一段时间了。但还没有弄清楚程序是如何执行的 例如,采用这个简单的程序: // file app.js` var fs = require('fs'); var fileBuffer = fs.readFileSync('./sample.txt'); fs.readFile('./resource.json', function (er, data) { console.log(data); }); console.log(fileBuffer)

我研究node.js已经有一段时间了。但还没有弄清楚程序是如何执行的

例如,采用这个简单的程序:

// file app.js`

var fs = require('fs');

var fileBuffer = fs.readFileSync('./sample.txt');

fs.readFile('./resource.json', function (er, data) {    
    console.log(data);
});

console.log(fileBuffer);
现在,当我们在命令行中输入node app.js时,在程序结束之前,幕后会发生什么

具体来说,我想了解:

事件循环如何处理前两个阻塞I/O调用,以及; 关于异步。函数,一旦I/O完成,它将向事件队列发送什么以及如何&何时调用附加的回调。 节点是单线程的。在执行阻塞调用时,不会发生任何事情。不会触发任何事件,也不会执行任何回调。这就是为什么在节点世界中总是非常不鼓励阻塞调用。ReadFileSync应该用于命令行脚本,或者在引导应用程序时使用。 不太清楚你到底在问什么。在您列出的示例中,回调函数接收两个非常常见的参数。一个是null或错误实例,另一个是数据。此方法接收缓冲区作为第二个参数。将缓冲区转换为字符串的一种简单快捷的方法是data.toString,它将字节列表转换为UTF-8编码的字符串。 您会注意到几乎每个节点模块都使用这个回调签名;一个可选错误,然后返回数据

从命令行运行时,您会注意到,当最后一个IO操作完成时,应用程序将退出。节点会跟踪它本身。构建web应用程序时,打开的HTTPS连接将使进程保持“繁忙”

节点是单线程的。在执行阻塞调用时,不会发生任何事情。不会触发任何事件,也不会执行任何回调。这就是为什么在节点世界中总是非常不鼓励阻塞调用。ReadFileSync应该用于命令行脚本,或者在引导应用程序时使用。 不太清楚你到底在问什么。在您列出的示例中,回调函数接收两个非常常见的参数。一个是null或错误实例,另一个是数据。此方法接收缓冲区作为第二个参数。将缓冲区转换为字符串的一种简单快捷的方法是data.toString,它将字节列表转换为UTF-8编码的字符串。 您会注意到几乎每个节点模块都使用这个回调签名;一个可选错误,然后返回数据


从命令行运行时,您会注意到,当最后一个IO操作完成时,应用程序将退出。节点会跟踪它本身。在构建web应用程序时,打开的HTTPS连接将使进程保持“繁忙”。

阻塞I/O只是因为:呼叫在完成之前不会返回。因此,当您调用readFileSync时,它会直接调用OS API来读取文件,并且只有在将整个内容复制到缓冲区后,控制才会返回到JS

对于阻塞同步函数,事件循环与I/O的处理方式完全无关。这就是同步I/O在节点中非常糟糕的原因——因为事件循环没有运行,所以它阻止了其他任何事情的发生

另一方面,像readFile这样的普通异步I/O函数只是简单地请求完成I/O,然后返回。I/O的具体执行方式取决于I/O请求的性质,即是文件系统还是网络,但都是由C库在单独的线程上处理的

网络I/O实际上是使用主机操作系统的本机执行的,它非常快,并且不需要为每个连接使用单独的线程

每当操作系统报告网络I/O活动时,事件都会被放入队列中。在事件循环的下一个滴答声中,事件被拾取并发送到相应的JavaScript函数。C代码可以获取对JS函数的引用并调用它们

文件I/O使用正常的阻塞系统I/O调用完成。每个I/O请求都放置在中,并由线程池执行。这里的关键是阻塞I/O是在C中完成的,在一个与JavaScript线程分离的线程上

当阻塞I/O调用返回时,结果被放入队列中。同样,事件在下一个刻度上被调度

节点保留挂起的工作请求的引用计数,如I/O、计时器和侦听服务器。如果勾号结束时为零,则进程退出。在某些情况下,可以使用从引用计数中显式删除活动请求

其他一些相关答案将有助于进一步解释其中一些概念:

最后,让我们浏览一下您的示例程序。下面就是发生的情况:

require'fs'提供对已初始化的fs模块的引用。内置模块是特殊的,不需要I/O来加载。 fs.readFileSync调用操作系统文件API。直到文件内容被复制到内存中,它才会返回。 fs.readFile添加一个包含文件名和引用的项 ce调用libuv工作队列的回调,然后立即返回。因为工作队列上没有任何内容,所以I/O将很快在一个单独的线程上开始。 fileBuffer的内容将记录到控制台。 此时,控件到达程序的末尾,因此它返回到节点。这是第一个滴答声的结尾。由于存在挂起的I/O,所以节点不会退出。 节点进入空闲状态。事件循环在不确定循环中检查事件队列中的新项。许多滴答声经过,直到。。。 异步I/O操作最终完成并将结果添加到事件队列中。下一个勾号将事件从队列中弹出,并调用最初传递给readFile的回调函数。 数据内容被记录到控制台,回调函数返回。 滴答声已经结束,没有更多的未决工作。节点退出。
阻塞I/O只是:调用在完成之前不会返回。因此,当您调用readFileSync时,它会直接调用OS API来读取文件,并且只有在将整个内容复制到缓冲区后,控制才会返回到JS

对于阻塞同步函数,事件循环与I/O的处理方式完全无关。这就是同步I/O在节点中非常糟糕的原因——因为事件循环没有运行,所以它阻止了其他任何事情的发生

另一方面,像readFile这样的普通异步I/O函数只是简单地请求完成I/O,然后返回。I/O的具体执行方式取决于I/O请求的性质,即是文件系统还是网络,但都是由C库在单独的线程上处理的

网络I/O实际上是使用主机操作系统的本机执行的,它非常快,并且不需要为每个连接使用单独的线程

每当操作系统报告网络I/O活动时,事件都会被放入队列中。在事件循环的下一个滴答声中,事件被拾取并发送到相应的JavaScript函数。C代码可以获取对JS函数的引用并调用它们

文件I/O使用正常的阻塞系统I/O调用完成。每个I/O请求都放置在中,并由线程池执行。这里的关键是阻塞I/O是在C中完成的,在一个与JavaScript线程分离的线程上

当阻塞I/O调用返回时,结果被放入队列中。同样,事件在下一个刻度上被调度

节点保留挂起的工作请求的引用计数,如I/O、计时器和侦听服务器。如果勾号结束时为零,则进程退出。在某些情况下,可以使用从引用计数中显式删除活动请求

其他一些相关答案将有助于进一步解释其中一些概念:

最后,让我们浏览一下您的示例程序。下面就是发生的情况:

require'fs'提供对已初始化的fs模块的引用。内置模块是特殊的,不需要I/O来加载。 fs.readFileSync调用操作系统文件API。直到文件内容被复制到内存中,它才会返回。 fs.readFile将包含文件名和回调引用的项添加到libuv工作队列,然后立即返回。因为工作队列上没有任何内容,所以I/O将很快在一个单独的线程上开始。 fileBuffer的内容将记录到控制台。 此时,控件到达程序的末尾,因此它返回到节点。这是第一个滴答声的结尾。由于存在挂起的I/O,所以节点不会退出。 节点进入空闲状态。事件循环在不确定循环中检查事件队列中的新项。许多滴答声经过,直到。。。 异步I/O操作最终完成并将结果添加到事件队列中。下一个勾号将事件从队列中弹出,并调用最初传递给readFile的回调函数。 数据内容被记录到控制台,回调函数返回。 滴答声已经结束,没有更多的未决工作。节点退出。
解释得很好!但是返回到事件队列的结果是否包含对要调用的回调的引用,或者下一次勾选的事件循环如何调用回调函数。是的,工作请求可以包含对应该调用的JS回调函数的引用。在libuv中,是在JS线程和工作线程之间传递的一段数据。它可以包含一个V8 Persistent,它是对JS函数的引用。当JS请求工作时,C将作为参数传入的函数引用添加到baton中。然后,在另一个不得接触V8参考的螺纹上进行工作。当工作完成时,V8线程上的C代码可以调用保存在baton.OK中的回调!如果我需要定义任何不依赖于任何I/O的函数,那么在异步中声明它有意义吗。风格类似于:var calc=functiona,b,cb{..var
res=计算值;cbres;}无论如何,它都将在应用程序线程中处理。是吗?这是纯JS,所以它自然会发生在JS线程上。无论您是以异步方式编写它,还是传递回调参数,实际上都是一个判断调用。这样做可以灵活地在将来轻松添加异步操作,比如在外部存储ie Redis中缓存数据。另一方面,如果您非常确定永远不需要向函数添加异步操作,那么只需同步返回一个值即可简化它的使用。解释得很好!但是返回到事件队列的结果是否包含对要调用的回调的引用,或者下一次勾选的事件循环如何调用回调函数。是的,工作请求可以包含对应该调用的JS回调函数的引用。在libuv中,是在JS线程和工作线程之间传递的一段数据。它可以包含一个V8 Persistent,它是对JS函数的引用。当JS请求工作时,C将作为参数传入的函数引用添加到baton中。然后,在另一个不得接触V8参考的螺纹上进行工作。当工作完成时,V8线程上的C代码可以调用保存在baton.OK中的回调!如果我需要定义任何不依赖于任何I/O的函数,那么在异步中声明它有意义吗。风格类似于:var calc=function,b,cb{..var res=computedValue;cbres;}它无论如何都会在应用程序线程中处理。是吗?这是纯JS,所以它自然会发生在JS线程上。无论您是以异步方式编写它,还是传递回调参数,实际上都是一个判断调用。这样做可以灵活地在将来轻松添加异步操作,比如在外部存储ie Redis中缓存数据。另一方面,如果您非常确定永远不需要向函数中添加异步操作,则只需同步返回一个值即可简化其使用。很抱歉,您没有了解我的问题,但我想从内部了解一下node执行程序的算法。我只是想有一个清晰的思维模型,来描述事物是如何由内而外运作的@josh3736提供了一个很好的情况。很抱歉,您没有理解我的问题,但我想从内部了解一下node执行程序的算法。我只是想有一个清晰的思维模型,来描述事物是如何由内而外运作的@josh3736很好地描述了事情。