源本科技 | 码上会

Node.js 模块化概述

2026/04/10
1
0

引言

模块化是软件设计的核心思想,指将一个复杂的系统,按照功能单一、独立可复用的原则,逐层拆分为多个小单元。这些单元可自由组合、替换、维护,最终拼装成完整的系统。 模块化开发极大提升了代码的复用性、可维护性、可扩展性,是前端和 Node.js 开发中必须掌握的核心规范。

现实生活中的模块化

Switch 游戏机是模块化设计的经典案例,整机由多个独立组件模块组成,各模块分工明确、可自由组合:

  • 主机模块: 集成处理器、内存等核心硬件,负责游戏运行与系统调度,支持独立硬件升级

  • 控制器模块: 包含 Joy-Con、Pro 控制器等独立模块,可灵活连接主机,适配多种操作方式

  • 显示模块: 独立显示屏,可便携使用,也可搭配底座连接电视,适配多场景体验

  • 底座模块: 作为视频输出与充电的桥梁,切换主机工作模式,无需改动整机结构

模块化设计为电子产品带来三大核心优势:

  • 可维护性: 单个模块故障,仅需替换对应模块,无需报废整机

  • 可升级性: 独立升级硬件模块,延长设备生命周期

  • 定制化: 用户可按需组合模块,实现个性化使用需求

编程领域中的模块化

编程中的模块化,是将大型项目拆分为多个独立、单一职责的代码模块。每个模块封装内部实现,仅对外暴露固定接口,模块之间通过规范的方式通信。

模块化的核心价值:

  • 封装性: 隐藏模块内部实现细节,仅暴露必要接口,降低代码耦合

  • 复用性: 一次编写,多处调用,避免重复开发,提升开发效率

  • 可维护性: 单个模块修改不影响其他模块,便于调试与迭代

  • 可扩展性: 新增功能只需添加新模块,无需修改原有代码

  • 按需加载: 动态加载所需模块,减少资源占用,提升程序性能

模块化规范

模块化规范是代码拆分、引用、暴露成员时必须遵守的统一规则,解决不同开发者、不同模块之间的兼容问题。 规范核心约束两点:

  1. 如何引入其他模块(如 requireimport

  2. 如何暴露模块内部的成员(如 module.exportsexport

遵守统一规范的意义:

  • 消除代码兼容问题,模块可跨项目、跨团队通用

  • 降低协作成本,所有开发者遵循相同的编码规则

  • 提升代码可读性与可维护性

Node.js 中的模块化

Node.js 采用 CommonJS 模块化规范,是服务端 JavaScript 模块化的标准方案,也是 Node.js 最基础的核心特性。

模块的分类

Node.js 按照模块来源,将模块划分为三大类,使用方式各有区别:

  • 内置模块:Node.js 官方自带的核心模块,无需安装、直接引入 示例:fspathhttppath

  • 自定义模块:开发者自己编写的 .js 文件,每个文件都是一个独立模块

  • 第三方模块:社区开发者发布的模块,需通过 npm/pnpm 下载安装后使用 示例:momentaxiosexpress

加载模块

Node.js 提供全局方法 require(),用于加载内置模块、自定义模块、第三方模块

重要特性:使用 require() 加载模块时,会立即执行被加载模块的代码。

// 1. 加载内置模块(直接写模块名)
const fs = require('fs');
const path = require('path');

// 2. 加载自定义模块(必须写相对路径 ./  ../,后缀 .js 可省略)
const custom = require('./custom');
const utils = require('./utils/index');

// 3. 加载第三方模块(直接写模块名)
const moment = require('moment');

路径加载规则

  • 相对路径必须以 ./../ 开头,不可省略

  • 自定义模块可省略文件后缀 .js.json

  • 加载文件夹时,自动读取文件夹下的 index.js

模块作用域

模块作用域是 Node.js 模块化的核心保护机制: 在自定义模块中定义的变量、函数、对象,默认只能在当前模块内访问,外部文件无法直接使用,这种作用域隔离称为模块作用域

核心作用:解决全局变量污染

全局变量污染是指多个文件的变量互相覆盖、干扰,导致程序异常。 Node.js 为每个模块创建独立作用域,彻底避免了该问题。

代码示例

// a.js 模块
const num = 10;
const fn = () => console.log(num);

// b.js 模块
const num = 20;
// 无法访问 a.js 中的 num 和 fn

共享模块成员

模块作用域默认隔离成员,若需要让外部模块使用当前模块的变量 / 函数,必须通过 Node.js 提供的对象主动暴露成员

module 对象

每个 Node.js 模块中,都内置一个 module 对象,存储当前模块的所有信息。 我们可以直接打印查看模块详情:

// 任意 .js 文件中
console.log(module);

执行后输出结果:

{
  id: '.',
  path: 'D:\\Workspaces\\Study\\hello-nodejs',
  exports: {}, // 暴露成员的核心对象
  filename: 'D:\\Workspaces\\Study\\hello-nodejs\\hello-module.js',
  loaded: false,
  children: [],
  paths: [ 'node_modules 搜索路径' ]
}

module.exports

module.exports 是模块对外暴露成员的核心接口。 使用 require() 导入模块时,最终获取的就是该模块的 module.exports 对象。

使用示例

// mathModule.js 自定义模块
const add = (a, b) => a + b;
const multiply = (a, b) => a * b;

// 向外部暴露成员
module.exports = {
  add,
  multiply
};
// main.js 导入并使用
const math = require('./mathModule');
console.log(math.add(2, 3));      // 5
console.log(math.multiply(2, 3)); // 6

exports 对象

exportsmodule.exports快捷引用,本质上 exports = module.exports。 适合逐个添加暴露成员,简化代码编写。

使用示例

// userModule.js
// 逐个暴露方法
exports.sayHello = (name) => `Hello, ${name}!`;
exports.getUserInfo = (id) => ({ id, name: 'John' });
// main.js
const user = require('./userModule');
console.log(user.sayHello('Alice'));

注意事项

  1. 最终生效规则require() 导入的结果,永远以 module.exports 指向的对象为准

  2. 禁止混用:同一个模块中,不要同时使用 exportsmodule.exports,容易导致成员丢失

  3. 赋值限制:直接给 exports 赋值会断开与 module.exports 的引用,导致暴露失效

错误示例

// 错误:直接覆盖 exports,失效
exports = { name: 'test' }

模块化规范

Node.js 严格遵循 CommonJS 模块化规范,这是服务端 JavaScript 的标准模块化方案,核心规则:

  • 每个 .js 文件都是一个独立的模块,拥有独立的模块作用域

  • 模块内部内置 module 对象,代表当前模块实例

  • module.exports 是模块对外暴露成员的唯一接口

  • 使用全局函数 require() 加载其他模块

  • 模块加载是同步执行的,加载完成后才会执行后续代码

补充说明

最佳实践

  1. 一个模块只实现一个功能,遵循单一职责原则

  2. 优先使用 module.exports 暴露成员,避免混用导致错误

  3. 加载自定义模块必须使用相对路径,杜绝路径错误

  4. 模块命名语义化,便于维护和调用

常见误区

  1. 忘记写 ./ 加载自定义模块,导致 Node.js 误识别为第三方模块

  2. 直接赋值 exports,导致暴露的成员无法被外部访问

  3. 混淆模块作用域,误以为全局定义的变量可以跨模块使用


总结

  1. 模块化是代码拆分与复用的核心思想,Node.js 基于 CommonJS 规范实现模块化

  2. 模块分为内置、自定义、第三方三类,统一使用 require() 加载

  3. 模块作用域隔离成员,避免全局污染,通过 module.exports 对外共享成员

  4. exportsmodule.exports 的快捷方式,导入结果永远以 module.exports 为准