Node.js 是一个基于 Chrome V8 引擎的 JavaScript 运行时,它使用了一个事件驱动、非阻塞 I/O 模型,使其轻量又高效。在 Node.js 中,模块系统扮演着重要的角色,本文将为大家介绍 Node.js 中的模块系统。
1. 模块定义
在 Node.js 中,每个文件都可以看作一个模块,模块里面的所有变量、函数等都是私有的,对外不可见。要在其他文件中使用模块里的变量或函数,需要通过导出模块的方式将其暴露出去。
1.1 导出模块
Node.js 支持两种导出模块的方式:
- module.exports
- exports
两种方式的基本用法相同,但有一点区别:module.exports 是真正执行导出操作的对象,而 exports 只是 module.exports 的一个引用。因此,如果直接对 exports 赋值,会断开两者之间的引用关系。比如:
// module.js
exports.a = 1;
exports.b = 2;
// main.js
var m = require('./module');
console.log(m.a); // 1
exports = {}; // 这里会断开导出对象 exports 和 module.exports 的引用关系
console.log(m.a); // 1
console.log(exports); // {}, exports 和 module.exports 已不再是同一个对象
为了避免这种错误的发生,一般使用 module.exports 导出模块。
1.2 代码示例
下面是一个示例模块,它将两个数相加并返回相加结果:
// add.js
function add(a, b) {
return a + b;
}
module.exports = add;
在其他文件中,可以使用 require 函数载入该模块并调用该模块导出的函数:
// main.js
var add = require('./add');
console.log(add(1, 2)); // 3
2. 模块查找机制
在使用 require 函数时,Node.js会根据引用模块的路径查找模块,查找规则如下:
- 如果是 Node.js 自带的核心模块,比如 fs、http 等,直接加载该模块。
- 如果是 "./"、"../"、"/" 开头的模块标识符,则按照文件路径查找模块。如果路径以文件名结尾,比如 require('./module.js'),则会直接按照该文件名查找模块;否则,会在该路径下查找 package.json 文件,如果找到则按照 package.json 文件中的 main 属性指定的文件名查找模块,否则会依次查找路径下是否有 .js、.json、.node 文件,找到则直接加载该文件作为模块。
- 如果是第三方模块,比如 express,Node.js 会先在当前目录下的 node_modules 文件夹中查找该模块,如果没有找到则会依次往上层目录查找,直到找到该模块位置。查找到该模块后,会先查找该模块下是否有 package.json 文件,如果找到则按照 package.json 文件中的 main 属性指定的文件名查找模块,否则会依次查找路径下是否有 .js、.json、.node 文件,找到则直接加载该文件作为模块。如果依次查找所有目录仍然没有找到,则会报错。
3. 模块缓存
在 Node.js 中,每个模块在第一次被 require 加载时会被缓存,之后再次 require 该模块时就不会重新加载了,而是直接从缓存中获取。这么做的好处是避免了重复加载,提升了应用的性能。
3.1 示例说明
下面是一个例子,首次 require 该模块时会输出 Module a 加载,再次 require 时则直接获取缓存的模块对象:
// a.js
console.log('Module a 加载');
module.exports = 'a';
// main.js
const a1 = require('./a');
const a2 = require('./a');
console.log(a1, a2); // a a
3.2 清除模块缓存
如果想要强制重新加载一个模块,可以使用 require.cache 对象删除该模块的缓存对象,如下所示:
delete require.cache[require.resolve('./a')];
const a3 = require('./a'); // Module a 加载
4. 小结
本文介绍了 Node.js 中的模块系统,包括模块定义、模块查找机制和模块缓存等方面的知识点。模块系统是 Node.js 架构的重要组成部分,能够使开发者更加方便地管理应用的代码,同时也能够提升应用的性能表现。在实际开发中,我们应该注意好模块系统的使用,遵从好模块设计原则,同时也要掌握好模块间的调用方式和模块导出方式,才能构建出更加可靠、高效的应用。