Search K
Appearance
Appearance
📊 SEO元描述:2024年最新Node.js模块系统教程,详解CommonJS规范、require/module.exports、ES6模块import/export、模块加载机制。包含完整代码示例,适合Node.js开发者掌握模块化开发。
核心关键词:Node.js模块系统、CommonJS、require、module.exports、ES6模块、import export、模块加载
长尾关键词:Node.js怎么导入模块、CommonJS和ES6模块区别、require和import区别、Node.js模块化开发、模块加载机制原理
通过本节Node.js模块系统基础的学习,你将系统性掌握:
什么是CommonJS?为什么Node.js选择它?CommonJS是Node.js的默认模块系统,提供了require()和module.exports语法,让JavaScript具备了模块化能力。它的同步加载特性非常适合服务器端开发的需求。
💡 设计理念:CommonJS设计时考虑的是服务器端环境,模块文件都在本地,同步加载不会造成阻塞问题。
// 🎉 CommonJS模块系统完整示例
// === 基础导出方式 ===
// math.js - 数学工具模块
function add(a, b) {
return a + b;
}
function subtract(a, b) {
return a - b;
}
function multiply(a, b) {
return a * b;
}
function divide(a, b) {
if (b === 0) {
throw new Error('除数不能为零');
}
return a / b;
}
// 方式1:逐个导出
exports.add = add;
exports.subtract = subtract;
// 方式2:批量导出
module.exports = {
add,
subtract,
multiply,
divide,
// 导出常量
PI: 3.14159,
E: 2.71828,
// 导出配置对象
config: {
precision: 10,
rounding: 'half-up'
}
};
// 方式3:导出单个函数
// module.exports = function calculator(operation, a, b) {
// switch(operation) {
// case 'add': return a + b;
// case 'subtract': return a - b;
// case 'multiply': return a * b;
// case 'divide': return a / b;
// default: throw new Error('不支持的操作');
// }
// };
// 方式4:导出类
class Calculator {
constructor() {
this.history = [];
}
add(a, b) {
const result = a + b;
this.history.push(`${a} + ${b} = ${result}`);
return result;
}
subtract(a, b) {
const result = a - b;
this.history.push(`${a} - ${b} = ${result}`);
return result;
}
getHistory() {
return this.history.slice(); // 返回副本
}
clearHistory() {
this.history = [];
}
}
// 如果要导出类,取消下面的注释
// module.exports = Calculator;// 🎉 require导入的各种方式
// app.js - 主应用文件
// === 基础导入方式 ===
// 1. 导入整个模块
const math = require('./math');
console.log('加法结果:', math.add(5, 3));
console.log('PI值:', math.PI);
// 2. 解构导入
const { add, subtract, multiply } = require('./math');
console.log('解构导入加法:', add(10, 5));
// 3. 重命名导入
const { add: addition, subtract: subtraction } = require('./math');
console.log('重命名导入:', addition(7, 3));
// 4. 导入并立即使用
console.log('立即使用:', require('./math').divide(20, 4));
// === 不同类型模块的导入 ===
// 1. 导入核心模块
const fs = require('fs');
const path = require('path');
const http = require('http');
const crypto = require('crypto');
// 2. 导入第三方模块
// const express = require('express');
// const lodash = require('lodash');
// const moment = require('moment');
// 3. 导入本地模块(相对路径)
const utils = require('./utils/helpers');
const config = require('./config/database');
// 4. 导入JSON文件
const packageInfo = require('./package.json');
console.log('应用名称:', packageInfo.name);
// 5. 导入目录(会查找index.js)
const controllers = require('./controllers');
// === 条件导入 ===
const environment = process.env.NODE_ENV || 'development';
let dbConfig;
if (environment === 'production') {
dbConfig = require('./config/production');
} else if (environment === 'test') {
dbConfig = require('./config/test');
} else {
dbConfig = require('./config/development');
}
console.log('数据库配置:', dbConfig);
// === 动态导入 ===
function loadPlugin(pluginName) {
try {
const plugin = require(`./plugins/${pluginName}`);
console.log(`插件 ${pluginName} 加载成功`);
return plugin;
} catch (error) {
console.error(`插件 ${pluginName} 加载失败:`, error.message);
return null;
}
}
// 根据配置动态加载插件
const enabledPlugins = ['logger', 'validator', 'cache'];
const plugins = enabledPlugins
.map(loadPlugin)
.filter(plugin => plugin !== null);
console.log(`成功加载 ${plugins.length} 个插件`);// 🎉 Node.js模块加载机制详解
// === 模块解析算法演示 ===
class ModuleResolver {
// 模拟Node.js的模块解析过程
static resolveModule(id, fromPath = process.cwd()) {
console.log(`\n=== 解析模块: ${id} ===`);
// 1. 核心模块检查
const coreModules = ['fs', 'path', 'http', 'crypto', 'util'];
if (coreModules.includes(id)) {
console.log('✅ 核心模块,直接返回');
return `[Core Module: ${id}]`;
}
// 2. 相对路径或绝对路径
if (id.startsWith('./') || id.startsWith('../') || id.startsWith('/')) {
console.log('📁 相对/绝对路径模块');
return this.resolveFilePath(id, fromPath);
}
// 3. node_modules查找
console.log('📦 第三方模块,查找node_modules');
return this.resolveNodeModules(id, fromPath);
}
static resolveFilePath(id, fromPath) {
const path = require('path');
const fs = require('fs');
const basePath = path.resolve(fromPath, id);
const extensions = ['', '.js', '.json', '.node'];
for (const ext of extensions) {
const fullPath = basePath + ext;
console.log(` 检查文件: ${fullPath}`);
try {
if (fs.statSync(fullPath).isFile()) {
console.log(` ✅ 找到文件: ${fullPath}`);
return fullPath;
}
} catch (error) {
// 文件不存在,继续检查
}
}
// 检查是否为目录(查找index文件)
try {
if (fs.statSync(basePath).isDirectory()) {
console.log(` 📁 目录存在,查找index文件`);
return this.resolveIndexFile(basePath);
}
} catch (error) {
// 目录不存在
}
throw new Error(`模块未找到: ${id}`);
}
static resolveIndexFile(dirPath) {
const path = require('path');
const fs = require('fs');
const indexFiles = ['index.js', 'index.json', 'index.node'];
for (const indexFile of indexFiles) {
const indexPath = path.join(dirPath, indexFile);
console.log(` 检查index文件: ${indexPath}`);
try {
if (fs.statSync(indexPath).isFile()) {
console.log(` ✅ 找到index文件: ${indexPath}`);
return indexPath;
}
} catch (error) {
// index文件不存在,继续检查
}
}
throw new Error(`目录中未找到index文件: ${dirPath}`);
}
static resolveNodeModules(id, fromPath) {
const path = require('path');
const fs = require('fs');
let currentPath = fromPath;
while (currentPath !== path.dirname(currentPath)) {
const nodeModulesPath = path.join(currentPath, 'node_modules', id);
console.log(` 检查: ${nodeModulesPath}`);
try {
if (fs.statSync(nodeModulesPath).isDirectory()) {
console.log(` ✅ 找到模块目录: ${nodeModulesPath}`);
// 检查package.json的main字段
const packageJsonPath = path.join(nodeModulesPath, 'package.json');
try {
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
const mainFile = packageJson.main || 'index.js';
const mainPath = path.join(nodeModulesPath, mainFile);
console.log(` 📄 package.json指定入口: ${mainPath}`);
return mainPath;
} catch (error) {
// package.json不存在或格式错误,使用默认index
return this.resolveIndexFile(nodeModulesPath);
}
}
} catch (error) {
// 模块目录不存在,继续向上查找
}
currentPath = path.dirname(currentPath);
}
throw new Error(`模块未找到: ${id}`);
}
}
// === 模块缓存机制演示 ===
class ModuleCacheDemo {
static demonstrateCache() {
console.log('\n=== 模块缓存机制演示 ===');
// 第一次require
console.log('第一次require math模块:');
const math1 = require('./math');
// 第二次require(从缓存返回)
console.log('第二次require math模块:');
const math2 = require('./math');
// 验证是同一个对象
console.log('两次require是同一个对象:', math1 === math2);
// 查看模块缓存
console.log('\n当前模块缓存:');
Object.keys(require.cache).forEach(modulePath => {
console.log(` ${modulePath}`);
});
// 清除模块缓存
const mathModulePath = require.resolve('./math');
delete require.cache[mathModulePath];
console.log('\n清除math模块缓存后,重新require:');
const math3 = require('./math');
console.log('清除缓存后是否为同一对象:', math1 === math3);
}
// 热重载实现示例
static createHotReload(modulePath) {
const fs = require('fs');
const path = require('path');
let cachedModule = require(modulePath);
const absolutePath = require.resolve(modulePath);
// 监听文件变化
fs.watchFile(absolutePath, (curr, prev) => {
console.log(`\n📝 检测到文件变化: ${absolutePath}`);
// 清除缓存
delete require.cache[absolutePath];
try {
// 重新加载模块
cachedModule = require(modulePath);
console.log('✅ 模块热重载成功');
} catch (error) {
console.error('❌ 模块热重载失败:', error.message);
}
});
// 返回一个代理对象,总是使用最新的模块
return new Proxy({}, {
get(target, prop) {
return cachedModule[prop];
},
set(target, prop, value) {
cachedModule[prop] = value;
return true;
}
});
}
}
// === 循环依赖处理 ===
// a.js
console.log('a.js 开始执行');
exports.done = false;
const b = require('./b');
console.log('在 a.js 中,b.done =', b.done);
exports.done = true;
console.log('a.js 执行完成');
// b.js
console.log('b.js 开始执行');
exports.done = false;
const a = require('./a');
console.log('在 b.js 中,a.done =', a.done);
exports.done = true;
console.log('b.js 执行完成');
// 循环依赖的解决方案
class CircularDependencyHandler {
static handleCircularDeps() {
console.log('\n=== 循环依赖处理演示 ===');
// 方案1:延迟require
function delayedRequire() {
// 在函数内部require,而不是在模块顶部
const dependency = require('./some-module');
return dependency.someFunction();
}
// 方案2:使用事件发射器
const EventEmitter = require('events');
const moduleEvents = new EventEmitter();
// 模块A
function moduleA() {
moduleEvents.on('moduleB-ready', (moduleB) => {
console.log('模块A收到模块B就绪通知');
// 使用模块B的功能
});
// 模块A就绪后通知
moduleEvents.emit('moduleA-ready', { name: 'ModuleA' });
}
// 方案3:依赖注入
function createModuleWithDependencies(dependencies) {
return {
process(data) {
return dependencies.helper.transform(data);
},
validate(input) {
return dependencies.validator.check(input);
}
};
}
}
}
// 运行演示
try {
ModuleResolver.resolveModule('fs');
ModuleResolver.resolveModule('./math');
ModuleResolver.resolveModule('express');
} catch (error) {
console.error('模块解析错误:', error.message);
}
ModuleCacheDemo.demonstrateCache();ES6模块是JavaScript的官方模块标准,Node.js从v12开始支持,提供了更现代的模块化语法。
// 🎉 ES6模块系统详解
// === ES6模块导出方式 ===
// mathES6.mjs - ES6模块示例
// 注意:使用.mjs扩展名或在package.json中设置"type": "module"
// 1. 命名导出
export function add(a, b) {
return a + b;
}
export function subtract(a, b) {
return a - b;
}
export const PI = 3.14159;
export const E = 2.71828;
// 2. 批量导出
function multiply(a, b) {
return a * b;
}
function divide(a, b) {
if (b === 0) {
throw new Error('除数不能为零');
}
return a / b;
}
export { multiply, divide };
// 3. 重命名导出
function power(base, exponent) {
return Math.pow(base, exponent);
}
export { power as pow };
// 4. 默认导出
export default class Calculator {
constructor() {
this.history = [];
}
calculate(operation, a, b) {
let result;
switch(operation) {
case 'add':
result = add(a, b);
break;
case 'subtract':
result = subtract(a, b);
break;
case 'multiply':
result = multiply(a, b);
break;
case 'divide':
result = divide(a, b);
break;
default:
throw new Error('不支持的操作');
}
this.history.push(`${a} ${operation} ${b} = ${result}`);
return result;
}
getHistory() {
return [...this.history];
}
}
// 5. 混合导出
export const config = {
precision: 10,
rounding: 'half-up'
};// 🎉 ES6模块导入方式
// appES6.mjs - ES6模块导入示例
// 1. 导入默认导出
import Calculator from './mathES6.mjs';
// 2. 导入命名导出
import { add, subtract, PI } from './mathES6.mjs';
// 3. 导入所有导出
import * as math from './mathES6.mjs';
// 4. 重命名导入
import { pow as power } from './mathES6.mjs';
// 5. 混合导入
import Calculator, { add, PI, config } from './mathES6.mjs';
// 6. 动态导入
async function dynamicImport() {
try {
const mathModule = await import('./mathES6.mjs');
const result = mathModule.add(5, 3);
console.log('动态导入结果:', result);
} catch (error) {
console.error('动态导入失败:', error);
}
}
// 7. 条件导入
async function conditionalImport(moduleName) {
if (moduleName === 'math') {
const { add, subtract } = await import('./mathES6.mjs');
return { add, subtract };
} else if (moduleName === 'utils') {
const utils = await import('./utils.mjs');
return utils.default;
}
}
// === ES6模块的特性演示 ===
class ES6ModuleFeatures {
// 静态分析
static demonstrateStaticAnalysis() {
// ES6模块的导入导出在编译时确定
// 这使得工具可以进行静态分析,如tree-shaking
// ✅ 静态导入(可以被静态分析)
import { add } from './mathES6.mjs';
// ❌ 动态导入(无法静态分析,但运行时有效)
// const moduleName = 'mathES6.mjs';
// import { add } from moduleName; // 语法错误
}
// 实时绑定
static demonstrateLiveBinding() {
// ES6模块导出的是实时绑定,不是值的拷贝
// counter.mjs
let count = 0;
export function increment() {
count++;
}
export function getCount() {
return count;
}
// main.mjs
import { increment, getCount } from './counter.mjs';
console.log(getCount()); // 0
increment();
console.log(getCount()); // 1 - 实时更新
}
// 循环依赖处理
static handleCircularDependencies() {
// ES6模块对循环依赖有更好的支持
// moduleA.mjs
import { functionB } from './moduleB.mjs';
export function functionA() {
return 'A' + functionB();
}
// moduleB.mjs
import { functionA } from './moduleA.mjs';
export function functionB() {
return 'B';
}
// 这种循环依赖在ES6模块中可以正常工作
}
}
// === CommonJS vs ES6模块对比 ===
class ModuleSystemComparison {
static compareFeatures() {
const comparison = {
syntax: {
commonjs: 'require() / module.exports',
es6: 'import / export'
},
loading: {
commonjs: '同步加载',
es6: '异步加载(支持动态导入)'
},
analysis: {
commonjs: '运行时确定',
es6: '编译时确定(静态分析)'
},
binding: {
commonjs: '值拷贝',
es6: '实时绑定'
},
support: {
commonjs: 'Node.js原生支持',
es6: 'Node.js 12+支持,需要配置'
},
ecosystem: {
commonjs: '生态系统成熟',
es6: '逐渐普及,现代标准'
}
};
return comparison;
}
// 互操作性
static demonstrateInteroperability() {
// ES6模块导入CommonJS模块
// import fs from 'fs'; // 导入默认导出
// import { readFile } from 'fs'; // 命名导入(Node.js会自动处理)
// CommonJS模块导入ES6模块(需要动态导入)
async function importES6FromCommonJS() {
const mathModule = await import('./mathES6.mjs');
return mathModule.add(1, 2);
}
}
}
// 使用示例
const calc = new Calculator();
console.log('ES6模块计算结果:', calc.calculate('add', 10, 5));
console.log('命名导入结果:', add(3, 7));
console.log('PI值:', PI);
// 运行动态导入
dynamicImport();通过本节Node.js模块系统基础的学习,你已经掌握:
A: 可以,但有限制。ES6模块可以导入CommonJS模块,但CommonJS模块导入ES6模块需要使用动态import()。建议在新项目中统一使用ES6模块。
A: 在Node.js中,如果项目配置支持ES6模块(package.json中"type": "module"或.mjs文件),推荐使用import。否则使用require。新项目建议使用ES6模块。
A: 正常情况下不会。Node.js的模块缓存是必要的优化。只有在动态加载大量模块且不再使用时,才需要考虑手动清理缓存。
A: 可以通过重构代码结构、使用依赖注入、延迟require或事件发射器等方式解决。最好的方法是在设计阶段避免循环依赖。
A: Tree-shaking是构建工具的优化技术,可以移除未使用的代码。ES6模块的静态结构使得构建工具能够分析哪些导出被使用,从而优化最终的打包文件。
// 推荐的Node.js项目结构
project/
├── src/
│ ├── controllers/ // 控制器
│ ├── models/ // 数据模型
│ ├── services/ // 业务逻辑
│ ├── utils/ // 工具函数
│ ├── middleware/ // 中间件
│ └── config/ // 配置文件
├── tests/ // 测试文件
├── docs/ // 文档
├── package.json
└── README.md// 推荐的模块导出模式
// 1. 单一职责模块
export class UserService {
// 用户相关业务逻辑
}
// 2. 工具函数模块
export const formatDate = (date) => { /* ... */ };
export const validateEmail = (email) => { /* ... */ };
// 3. 配置模块
export default {
database: { /* ... */ },
server: { /* ... */ }
};"掌握Node.js模块系统是构建可维护应用的基础。无论是CommonJS还是ES6模块,理解其工作原理和最佳实践都能让你的代码更加模块化、可复用。接下来,让我们学习异步编程基础,这是Node.js开发的核心技能!"