Node.js 和模块范围:将文件读入内存的最有效方法

Posted

技术标签:

【中文标题】Node.js 和模块范围:将文件读入内存的最有效方法【英文标题】:Node.js and the Module Scope: Most Efficient Way to Read Files into Memory 【发布时间】:2019-01-10 08:18:14 【问题描述】:

我试图更好地理解 node.js 模块范围和变量实例化上下文中的 require。更具体地说,将文件读入内存。

我有一个带有模块的 http 服务器,它读取存储在代码库中的静态 sql 文件并执行其中包含的查询。例如:

'use strict';

const fs = require('fs')
const executeSql = require('./utils/execute-sql');

module.exports.getDataById = (id) => 
  const sql = fs.readFileSync(
    `./data-access/sql/getDataById.sql`, 'utf8'
  );

  return executeSql(sql, id);


module.exports.getDataByName = (name) => 
  const sql = fs.readFileSync(
    `./data-access/sql/getDataByName.sql`, 'utf8'
  );

  return executeSql(sql, name);

我的理解是,每次调用这些函数(getDataByIdgetDataByName)时,都会以阻塞的方式同步读取文件并阻塞执行线程。我知道我可以异步读取文件以避免这种情况,但我真正好奇的是,将 sql 变量拉出函数并进入模块范围是否意味着 readFile 操作只发生一次(当节点进程被实例化时)并且最终会更有效率。例如:

'use strict';

const fs = require('fs')
const executeSql = require('./utils/execute-sql');
const sql1 = fs.readFileSync(
  `./data-access/sql/getDataById.sql`, 'utf8'
);
const sql2 = fs.readFileSync(
  `./data-access/sql/getDataByName.sql`, 'utf8'
);

module.exports.getDataById = (id) => 
  return executeSql(sql1, id);


module.exports.getDataByName = (name) => 
  return executeSql(sql2, name);

我知道require 在节点进程初始化时同步加载模块,并在其他地方需要它们时进一步缓存这些模块,但我想了解的是标准变量声明是否不使用require 结果在一个类似实例化的内存引用中,它在节点进程的生命周期内持续存在,不需要在每次需要模块时重新实例化。

感谢您提供的任何见解。

【问题讨论】:

我看到您的另一个问题的答案有效但未被接受。尽量保持高提问/接受回答的比例,这样如果您对他们花在帮助您的时间给予积极反馈,人们会为您提供更多帮助。 感谢@JorgeFuentesGonzález 的反馈!我没有接受,因为我正在寻找不涉及重定向的其他答案。就选项而言,这是一个有效的答案,但并没有真正提供我在附加约束条件下寻找的答案。不过我会接受,尤其是考虑到距离写这篇文章已经很长时间了。 那太好了:-) 【参考方案1】:

你是对的。每次一个模块需要另一个模块时,只有第一次执行代码,其余时间它只返回缓存的exports,所以在你的例子中fs.readFileSync将运行一次(第一次有人需要它),node.js 将缓存exports 对象,并在下一个要求返回exports 对象,无需再次运行代码。

你可以用这样的东西来测试:

var mod = require("./myModule");
console.log(mod.nonExistantProperty); // This will log undefined
mod.nonExistantProperty = "yay";

var requireagain = require("./myModule");
console.log(requireagain.nonExistantProperty); // This will log yay

在第二次 require 中,不会再次执行模块代码,它只会返回缓存的对象,因此您可以看到在第二次 require 之前所做的修改。

有了这个信息,在你的第一个例子中,你在导出中返回函数,每次调用它们时都会执行它们的代码(很明显),所以如果你在函数中有一个 readfile 方法,它每次都会运行。

您的第二种方法通常用于提高性能,因为代码只运行一次(在第一次需要时),并且每次执行导出的函数时,它们都会访问已经缓存了文件内容的变量内容.感谢您得出这个结论 :-) 坚持下去。

【讨论】:

谢谢,@jorge-fuentes-gonzález!我想澄清一下。所以,简单地在模块范围内实例化一个变量引用实际上会将它缓存在模型的上下文中,还是我必须使用require 来获得这个缓存?所以,const sql1 = fs.readFileSync('./data-access/sql/getDataById.sql', 'utf8');const sql1 = require(fs.readFileSync('./data-access/sql/getDataById.sql', 'utf8')) 之间的区别 --- 不确定后面的代码是否真的有效,但我想你明白我的意思。 @willascend 不不,后者不起作用。正如你所说,上下文得到了chached,是的。

以上是关于Node.js 和模块范围:将文件读入内存的最有效方法的主要内容,如果未能解决你的问题,请参考以下文章

node.js:将文本文件读入数组。 (每一行都是数组中的一个项目。)

从内存中的字符串加载 node.js 模块

如何将文件逐行读入node.js中的数组[重复]

Node.js中的fs 模块

Node.js:如何创建付费节点模块?

node 之 fs流读写