Skip to content

Node.js模块系统2024:CommonJS与ES6模块完整指南

📊 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模块系统学习目标与核心收获

通过本节Node.js模块系统基础的学习,你将系统性掌握:

  • CommonJS规范深度理解:掌握Node.js默认模块系统的工作原理和最佳实践
  • require/module.exports精通:学会正确使用CommonJS的导入导出语法
  • ES6模块系统应用:理解import/export语法在Node.js中的使用方法
  • 模块加载机制原理:深入了解Node.js模块解析和缓存机制
  • 模块系统选择策略:掌握在不同场景下选择合适模块系统的方法
  • 模块化项目架构:建立大型Node.js项目的模块化组织结构

🎯 适合人群

  • Node.js初学者需要掌握模块化开发基础知识
  • 前端转后端开发者需要理解Node.js模块系统特点
  • 项目架构师需要设计合理的模块化项目结构
  • 团队技术负责人需要制定模块化开发规范

🌟 CommonJS规范:Node.js模块系统的基石

什么是CommonJS?为什么Node.js选择它?CommonJS是Node.js的默认模块系统,提供了require()module.exports语法,让JavaScript具备了模块化能力。它的同步加载特性非常适合服务器端开发的需求。

CommonJS核心特性

  • 🎯 同步加载:模块在require时立即加载,适合服务器端环境
  • 🔧 单例模式:模块只会被加载一次,后续require返回缓存
  • 💡 动态加载:可以在运行时根据条件加载不同模块
  • 📚 简单易用:语法直观,学习成本低
  • 🚀 成熟稳定:Node.js生态系统的标准,兼容性好

💡 设计理念:CommonJS设计时考虑的是服务器端环境,模块文件都在本地,同步加载不会造成阻塞问题。

require和module.exports详解

javascript
// 🎉 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;
javascript
// 🎉 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} 个插件`);

模块加载机制深入理解

javascript
// 🎉 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模块(import/export)

ES6模块是JavaScript的官方模块标准,Node.js从v12开始支持,提供了更现代的模块化语法。

javascript
// 🎉 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'
};
javascript
// 🎉 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模块系统学习总结与下一步规划

✅ 本节核心收获回顾

通过本节Node.js模块系统基础的学习,你已经掌握:

  1. CommonJS规范精通:理解require/module.exports的工作原理和最佳实践
  2. ES6模块系统应用:掌握import/export语法和现代模块化开发
  3. 模块加载机制:深入了解Node.js的模块解析、缓存和循环依赖处理
  4. 模块系统选择:能够根据项目需求选择合适的模块系统
  5. 模块化架构设计:具备设计大型项目模块化结构的能力

🎯 Node.js学习下一步

  1. 异步编程深入:学习Promise、async/await等现代异步编程技术
  2. 包管理器使用:掌握npm、yarn等包管理工具的高级用法
  3. 项目结构设计:学习大型Node.js项目的目录结构和模块组织
  4. 构建工具集成:了解Webpack、Rollup等构建工具与模块系统的集成

🔗 相关学习资源

💪 实践练习建议

  1. 模块重构:将现有项目重构为模块化结构
  2. 包开发实践:创建并发布一个npm包
  3. 模块系统迁移:尝试将CommonJS项目迁移到ES6模块
  4. 循环依赖解决:识别和解决项目中的循环依赖问题

🔍 常见问题FAQ

Q1: CommonJS和ES6模块可以混用吗?

A: 可以,但有限制。ES6模块可以导入CommonJS模块,但CommonJS模块导入ES6模块需要使用动态import()。建议在新项目中统一使用ES6模块。

Q2: 什么时候使用require,什么时候使用import?

A: 在Node.js中,如果项目配置支持ES6模块(package.json中"type": "module"或.mjs文件),推荐使用import。否则使用require。新项目建议使用ES6模块。

Q3: 模块缓存会导致内存泄漏吗?

A: 正常情况下不会。Node.js的模块缓存是必要的优化。只有在动态加载大量模块且不再使用时,才需要考虑手动清理缓存。

Q4: 如何解决循环依赖问题?

A: 可以通过重构代码结构、使用依赖注入、延迟require或事件发射器等方式解决。最好的方法是在设计阶段避免循环依赖。

Q5: ES6模块的tree-shaking是什么?

A: Tree-shaking是构建工具的优化技术,可以移除未使用的代码。ES6模块的静态结构使得构建工具能够分析哪些导出被使用,从而优化最终的打包文件。


🛠️ 模块化开发最佳实践

项目结构示例

javascript
// 推荐的Node.js项目结构
project/
├── src/
│   ├── controllers/     // 控制器
│   ├── models/         // 数据模型
│   ├── services/       // 业务逻辑
│   ├── utils/          // 工具函数
│   ├── middleware/     // 中间件
│   └── config/         // 配置文件
├── tests/              // 测试文件
├── docs/               // 文档
├── package.json
└── README.md

模块导出规范

javascript
// 推荐的模块导出模式

// 1. 单一职责模块
export class UserService {
    // 用户相关业务逻辑
}

// 2. 工具函数模块
export const formatDate = (date) => { /* ... */ };
export const validateEmail = (email) => { /* ... */ };

// 3. 配置模块
export default {
    database: { /* ... */ },
    server: { /* ... */ }
};

"掌握Node.js模块系统是构建可维护应用的基础。无论是CommonJS还是ES6模块,理解其工作原理和最佳实践都能让你的代码更加模块化、可复用。接下来,让我们学习异步编程基础,这是Node.js开发的核心技能!"