Node.js支持的两种JavaScript模块系统: CommonJS和ECMAScript模块(ES模块)。

CommonJS模块是Node.js最初采用的模块系统,它为Node.js提供了一种组织和打包JavaScript代码的方式。

通过这种模块系统,开发者可以将代码分割成不同的模块,每个模块都有自己的作用域,模块之间可以通过特定的语法进行相互引用。

例如,使用require()函数来加载其他模块,使用module.exports来导出模块的功能。

注意:CommonJS模块即将废弃,ES模块是未来的标准,Node.js将在未来的版本中支持ES模块。
本文仅作为学习备忘。

模块的基本概念

以下概念及测试代码仅限于 Node.js CommonJS modules。

在Node.js中,每个文件都被视为一个模块。

模块是一个包含JavaScript代码的文件,它可以包含变量、函数、类等。

每个 Node.js 文件(模块)在运行时会被包装成一个函数,函数参数中会传入 module 对象。它代表当前模块的元信息。

module 对象有以下属性:

  • module.id:模块的标识符,通常是模块文件的路径。
  • module.filename:模块文件的完整路径。
  • module.paths:模块搜索路径数组。
  • module.parent:父模块的引用。
  • module.children:子模块的引用数组。
  • module.exports:模块的导出对象。

我们可以创建一个 demo1.cjs 文件,在其中输出 module 对象的及属性:

javascript 复制代码
// demo1.cjs
console.log(module);

输出结果如下:

javascript 复制代码
{
  id: '.',
  path: 'D:\\itshizhan-developer\\webfront-apprentice\\nodejs-all\\\src',
  exports: {},
  filename: 'D:\\itshizhan-developer\\webfront-apprentice\\nodejs-all\\\src\\test.cjs',
  loaded: false,
  children: [],
  paths: [
    'D:\\itshizhan-developer\\webfront-apprentice\\nodejs-all\\\src\\node_modules',
    'D:\\itshizhan-developer\\webfront-apprentice\\nodejs-all\\\node_modules',
    'D:\\itshizhan-developer\\webfront-apprentice\\nodejs-all\\node_modules',
    'D:\\itshizhan-developer\\webfront-apprentice\\node_modules',
    'D:\\itshizhan-developer\\node_modules',
    'D:\\node_modules'
  ]
} 

module.exports

module.exports 是 Node.js 中用于导出模块的对象。

它允许我们将模块中的变量、函数或类等导出,以便其他模块可以使用。

默认情况下,module.exports 是一个空对象 {}, 我们可以将需要导出的内容赋值给它。

其他模块通过 require() 引入该模块时,实际获取的是 module.exports 的值

例如:假如有一个 a.cjs 文件,内容如下:

javascript 复制代码
// a.cjs
function add(a,b) {
 console.log(a+b)
}

在 a.cjs 中,我们没有使用 module.exports 导出 add 函数,而是直接在文件中定义了一个函数。

在其他模块中,我们可以通过 require() 引入 a.cjs 文件, 获取的默认就是 module.exports 的默认值,即空对象{}

例如:
在 b.cjs 文件中,我们可以通过 require() 引入 a.cjs 文件:

javascript 复制代码
// b.cjs
const a = require('./a.cjs');
console.log(a);

输出结果如下:

javascript 复制代码
{}

如果要想使用 a.cjs 中的 add 函数,我们可以将 add 函数赋值给 module.exports:

javascript 复制代码
// a.cjs
function add(a,b) {
 console.log(a+b)
} 
module.exports = add;

在 b.cjs 文件中,我们可以通过 require() 引入 a.cjs 文件,获取的是 add 函数:

javascript 复制代码
// b.cjs
const a = require('./a.cjs');
a(2,3) // 结果5

上述实际上是将函数直接导出为模块

将函数或类作为对象属性导出

加入有文件c.cjs,内容如下:

javascript 复制代码
// c.cjs
function add(a,b) {
 console.log(a+b)
}
class Person {
  constructor(name,age) {
    this.name = name;
    this.age = age;
  } 
  log() {
    console.log(this.name,this.age) 
  }
}
const mName = 'c.cjs'

module.exports = {
  add,
  Person,
  mName
}

在其他模块中,我们可以通过 require() 引入 c.cjs 文件,获取的是一个对象,该对象包含了 add 函数、Person 类和 mName 变量:

javascript 复制代码
// d.cjs

const c = require('./c.cjs');
console.log(c);

c.add(1,2);
const p = new c.Person('zhangsan',18);
p.log();
console.log(c.mName);

输出结果如下:

javascript 复制代码
{ add: [Function: add], Person: [class Person], mName: 'c.cjs' }
3
zhangsan 18
c.cjs

导出多个对象

javascript 复制代码
// 方式1:直接赋值对象
module.exports = { add, subtract };

// 方式2:动态添加属性
module.exports.add = (a, b) => a + b;
module.exports.subtract = (a, b) => a - b;

多次使用 module.exports

如果在一个模块中多次使用 module.exports,只有最后一次的赋值会生效。

exports 变量

本质:exports 是 module.exports 的一个引用。
初始时,exports === module.exports(即 exports 指向 module.exports 的内存地址)。

正确用法:通过 exports 添加属性(因为直接操作引用)。

错误用法:直接给 exports 赋值会切断它与 module.exports 的关联。

javascript 复制代码
// ✅ 正确:添加属性(操作引用)
exports.name = "Node.js";
exports.version = "20.x";
// 等价于:
module.exports.name = "Node.js";
module.exports.version = "20.x";

// ❌ 错误:直接赋值(切断关联)
exports = { name: "Node.js" }; // 此时 exports 不再指向 module.exports

关键规则:

✅ 最终导出的内容始终是 module.exports

✅ 如果同时操作 exports 和 module.exports,以 module.exports 为准。

✅ 统一使用 module.exports。避免混淆, 不建议使用单独使用 exports 。

require

上文已经使用,require 是用于导入其他模块的核心函数。

它接受一个模块标识符作为参数,返回被导入模块的导出对象。

模块标识符可以是相对路径或绝对路径,也可以是 Node.js 内置模块的名称。

require 本身用于导入整个模块,但可以通过解构赋值直接获取模块导出对象的特定属性。

例如:

javascript 复制代码
// math.js(导出一个对象)
module.exports = {
  add: (a, b) => a + b,
  multiply: (a, b) => a * b,
  PI: 3.14
};

// main.js(通过解构提取属性)
const { add, PI } = require('./math');

console.log(add(2, 3)); // 5
console.log(PI); // 3.14

也可以先导入整个模块对象,再通过点号(.)访问其属性:

javascript 复制代码
// main.js(先导入整个对象,再访问属性)
const math = require('./math');

console.log(math.add(2, 3)); // 5
console.log(math.PI); // 3.14

模块加载机制

在 Node.js 中,模块的加载是通过 require() 函数实现的。

require() 函数会根据模块的路径来加载模块,并返回模块的导出对象。

模块加载的过程如下:

  1. 解析模块路径:根据模块标识符(路径)解析模块文件。

  2. 缓存加载:检查模块是否已经被加载过,如果是,则直接返回缓存的模块。

  3. 创建模块对象:创建一个新的模块对象,用于存储模块的状态和导出对象。

  4. 执行模块代码:加载模块文件的内容,并执行模块代码。

  5. 导出模块:根据模块代码中的导出语句,将模块的导出对象赋值给 module.exports。

  6. 返回导出对象:返回 module.exports 的值,作为 require() 函数的返回值。

✅ 例如:

当通过 require('./myModule') 加载模块时:

Node.js 会执行该模块的代码。

返回该模块的 module.exports 值。

模块的代码只会执行一次(后续 require() 会从缓存中读取 module.exports)。

javascript 复制代码
// myModule.cjs
let count = 0;
module.exports = { 
  increment: () => ++count,
  getCount: () => count
};
console.log("count=",count);

在e.cjs 中导入myModule.cjs模块:

javascript 复制代码
const { increment, getCount } = require('./myModule.cjs');
increment();
console.log(getCount()); // 输出 1

输出结果如下:

javascript 复制代码
// 加载模块后,会执行该模块的代码
count= 0
1

模块查找规则

当使用 require('module') 时,Node.js 按以下顺序查找模块:

(1)核心模块

优先检查是否为 Node.js 内置模块(如 fspath),如果是则直接加载。

(2)文件模块

  1. 精确路径
javascript 复制代码
require('./utils.js'); // 加载当前目录下的 utils.js 文件
  1. 省略扩展名
javascript 复制代码
require('./utils'); // 依次尝试加载:utils.js → utils.json → utils.node
  1. 目录作为模块
javascript 复制代码
require('./mylib'); // 尝试加载:
// 1. ./mylib/package.json 中的 "main" 字段指定的文件
// 2. 若不存在 package.json,则加载 ./mylib/index.js
// 3. 若以上都不存在,则报错

(3)npm 包

  1. 从当前目录的 node_modules 文件夹中查找。
  2. 逐级向上层目录的 node_modules 查找,直到根目录。
复制代码
/project/src/index.js 中 require('lodash') 的查找路径:
/project/src/node_modules/lodash
/project/node_modules/lodash
/node_modules/lodash

模块缓存

Node.js 对模块实行缓存机制,同一模块仅在首次加载时执行一次,后续引用直接返回缓存结果。

javascript 复制代码
// module.js
console.log('Module initialized');
module.exports = { value: 42 };

// main.js
const mod1 = require('./module');
const mod2 = require('./module');
console.log(mod1 === mod2); // true(两次引用指向同一对象)

注意:若需多次执行模块代码,需手动清除缓存(不推荐,可能导致意外行为):

javascript 复制代码
delete require.cache[require.resolve('./module')];