前言
在JavaScript的发展历程中,模块化一直是一个重要的话题。CommonJS作为Node.js采用的模块系统,为服务端JavaScript开发奠定了坚实的基础。本文将深入探讨CommonJS的核心概念、工作原理、语法特性,以及它与现代ES6模块系统的区别和联系。
一、CommonJS模块系统概述
(一)什么是CommonJS
CommonJS是一个JavaScript模块化规范,最初由Mozilla的工程师Kevin Dangoor于2009年提出。它的目标是为JavaScript定义一套通用的模块API,使JavaScript能够在浏览器之外的环境中运行,特别是服务器端环境。
1. 设计目标
CommonJS规范的主要设计目标包括:
- 模块化开发:将代码分割成独立的模块,提高代码的可维护性
- 依赖管理:明确模块间的依赖关系,避免全局变量污染
- 代码复用:通过模块导入导出机制,实现代码的高效复用
- 服务端支持:为JavaScript在服务器端运行提供模块化支持
2. 核心特点
1 2 3 4 5 6 7 8 9 10 11
|
const fs = require('fs');
if (process.env.NODE_ENV === 'development') { const debug = require('debug'); }
const { count } = require('./counter');
|
(二)CommonJS在Node.js中的实现
Node.js是CommonJS规范最成功的实现之一,它将CommonJS作为默认的模块系统。
1. Node.js模块系统架构
1 2 3 4 5 6
| console.log(__filename); console.log(__dirname); console.log(module); console.log(exports); console.log(require);
|
2. 模块包装机制
Node.js在执行模块代码前,会将其包装在一个函数中:
1 2 3 4 5 6 7 8 9 10 11
| (function(exports, require, module, __filename, __dirname) { const fs = require('fs'); function readFile(path) { return fs.readFileSync(path, 'utf8'); } module.exports = { readFile }; });
|
二、CommonJS核心语法
(一)模块导出 - exports和module.exports
CommonJS提供了两种导出方式:exports
和module.exports
。
1. 使用exports导出
1 2 3 4 5 6 7 8 9 10 11 12 13
| exports.add = function(a, b) { return a + b; };
exports.subtract = function(a, b) { return a - b; };
exports.PI = 3.14159;
exports.multiply = (a, b) => a * b;
|
2. 使用module.exports导出
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| module.exports = { add: function(a, b) { return a + b; }, subtract: function(a, b) { return a - b; }, multiply(a, b) { return a * b; }, divide: (a, b) => { if (b === 0) { throw new Error('除数不能为零'); } return a / b; } };
|
3. 导出类和构造函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| class User { constructor(name, email) { this.name = name; this.email = email; this.createdAt = new Date(); } getInfo() { return `用户: ${this.name}, 邮箱: ${this.email}`; } static validateEmail(email) { const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; return emailRegex.test(email); } }
module.exports = User;
function Product(name, price) { this.name = name; this.price = price; this.id = Math.random().toString(36).substr(2, 9); }
Product.prototype.getPrice = function() { return `¥${this.price.toFixed(2)}`; };
module.exports = Product;
|
(二)模块导入 - require函数
require
函数是CommonJS中加载模块的核心机制。
1. 基本导入语法
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| const fs = require('fs'); const path = require('path'); const http = require('http');
const math = require('./math'); const utils = require('../utils/helper'); const config = require('/app/config');
const express = require('express'); const lodash = require('lodash'); const moment = require('moment');
|
2. 解构导入
1 2 3 4 5 6 7 8 9 10
| const { readFile, writeFile } = require('fs'); const { join, resolve, basename } = require('path');
const { add, subtract, PI } = require('./math');
const express = require('express'); const { Router } = express;
|
3. 条件导入和动态导入
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| let logger; if (process.env.NODE_ENV === 'production') { logger = require('./production-logger'); } else { logger = require('./development-logger'); }
function loadModule(moduleName) { try { return require(moduleName); } catch (error) { console.error(`无法加载模块 ${moduleName}:`, error.message); return null; } }
const dbDriver = loadModule(process.env.DB_TYPE || 'mysql');
|
三、CommonJS模块加载机制
(一)模块解析规则
Node.js按照特定的规则来解析模块路径:
1. 核心模块优先级最高
1 2 3 4
| const fs = require('fs'); const http = require('http'); const crypto = require('crypto');
|
2. 文件模块解析
1 2 3 4 5 6 7
| require('./math');
|
3. node_modules查找机制
1 2 3 4 5 6 7
| require('express');
|
(二)模块缓存机制
CommonJS具有强大的缓存机制,确保模块只被加载一次。
1. 缓存示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| let count = 0;
function increment() { return ++count; }
function getCount() { return count; }
module.exports = { increment, getCount };
const counter1 = require('./counter'); const counter2 = require('./counter');
console.log(counter1 === counter2);
counter1.increment(); console.log(counter2.getCount());
|
2. 清除缓存
1 2 3 4 5 6 7 8
| console.log(Object.keys(require.cache));
delete require.cache[require.resolve('./config')];
const freshConfig = require('./config');
|
四、CommonJS实践应用
(一)创建实用工具模块
1. 文件操作工具
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49
| const fs = require('fs'); const path = require('path');
async function readJsonFile(filePath) { try { const data = await fs.promises.readFile(filePath, 'utf8'); return JSON.parse(data); } catch (error) { throw new Error(`读取JSON文件失败: ${error.message}`); } }
async function writeJsonFile(filePath, data) { try { const jsonString = JSON.stringify(data, null, 2); await fs.promises.writeFile(filePath, jsonString, 'utf8'); } catch (error) { throw new Error(`写入JSON文件失败: ${error.message}`); } }
async function ensureDir(dirPath) { try { await fs.promises.access(dirPath); } catch (error) { await fs.promises.mkdir(dirPath, { recursive: true }); } }
module.exports = { readJsonFile, writeJsonFile, ensureDir };
|
2. 配置管理模块
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64
| const path = require('path'); const { readJsonFile } = require('./fileUtils');
class ConfigManager { constructor() { this.config = {}; this.loaded = false; } async load(configPath = './config.json') { try { const fullPath = path.resolve(configPath); this.config = await readJsonFile(fullPath); this.loaded = true; console.log('配置加载成功'); } catch (error) { console.error('配置加载失败:', error.message); this.config = this.getDefaultConfig(); this.loaded = true; } } get(key, defaultValue = null) { if (!this.loaded) { throw new Error('配置尚未加载,请先调用load()方法'); } return this.getNestedValue(this.config, key, defaultValue); } getNestedValue(obj, key, defaultValue) { const keys = key.split('.'); let current = obj; for (const k of keys) { if (current && typeof current === 'object' && k in current) { current = current[k]; } else { return defaultValue; } } return current; } getDefaultConfig() { return { server: { port: 3000, host: 'localhost' }, database: { host: 'localhost', port: 5432, name: 'myapp' } }; } }
module.exports = new ConfigManager();
|
(二)模块组织最佳实践
1. 目录结构组织
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| project/ ├── lib/ │ ├── database.js │ ├── logger.js │ └── validator.js ├── utils/ │ ├── fileUtils.js │ ├── dateUtils.js │ └── stringUtils.js ├── config/ │ ├── index.js │ ├── development.js │ └── production.js ├── models/ │ ├── User.js │ └── Product.js └── app.js
|
2. 索引文件模式
1 2 3 4 5 6 7 8 9 10 11 12 13
| const fileUtils = require('./fileUtils'); const dateUtils = require('./dateUtils'); const stringUtils = require('./stringUtils');
module.exports = { ...fileUtils, ...dateUtils, ...stringUtils };
const { readJsonFile, formatDate, capitalize } = require('./utils');
|
五、CommonJS与ES6模块对比
(一)语法差异
1. 导出语法对比
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
|
exports.add = function(a, b) { return a + b; }; exports.PI = 3.14159;
module.exports = { add: function(a, b) { return a + b; }, PI: 3.14159 };
export function add(a, b) { return a + b; } export const PI = 3.14159;
export default { add: function(a, b) { return a + b; }, PI: 3.14159 };
|
2. 导入语法对比
1 2 3 4 5 6 7 8
| const math = require('./math'); const { add, PI } = require('./math');
import math from './math'; import { add, PI } from './math'; import * as math from './math';
|
(二)核心差异
1. 加载时机
1 2 3 4 5 6 7 8
| console.log('开始加载'); const fs = require('fs'); console.log('加载完成');
import fs from 'fs'; console.log('开始执行');
|
2. 值传递方式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| let count = 0; exports.count = count; exports.increment = function() { count++; exports.count = count; };
const { count, increment } = require('./counter'); console.log(count); increment(); console.log(count);
export let count = 0; export function increment() { count++; }
import { count, increment } from './counter.mjs'; console.log(count); increment(); console.log(count);
|
3. 循环依赖处理
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
|
console.log('a开始'); exports.done = false; const b = require('./b'); console.log('在a中,b.done =', b.done); exports.done = true; console.log('a结束');
console.log('b开始'); exports.done = false; const a = require('./a'); console.log('在b中,a.done =', a.done); exports.done = true; console.log('b结束');
const a = require('./a'); const b = require('./b'); console.log('在main中,a.done =', a.done, ', b.done =', b.done);
|
(三)适用场景对比
1. CommonJS适用场景
1 2 3 4 5 6 7 8 9 10 11 12
| const express = require('express'); const app = express();
const dbType = process.env.DB_TYPE || 'mysql'; const db = require(`./drivers/${dbType}`);
if (process.env.NODE_ENV === 'development') { const debug = require('debug')('app'); }
|
2. ES6模块适用场景
1 2 3 4 5 6 7 8 9
| import React from 'react'; import { useState, useEffect } from 'react';
import { debounce } from 'lodash-es';
import type { User } from './types';
|
六、CommonJS进阶技巧
(一)模块包装和私有变量
1. 创建私有作用域
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| module.exports = (function() { let logLevel = 'info'; const logHistory = [];
function formatMessage(level, message) { const timestamp = new Date().toISOString(); return `[${timestamp}] ${level.toUpperCase()}: ${message}`; }
return { setLevel(level) { if (['debug', 'info', 'warn', 'error'].includes(level)) { logLevel = level; } },
log(message, level = 'info') { if (this.shouldLog(level)) { const formattedMessage = formatMessage(level, message); console.log(formattedMessage); logHistory.push(formattedMessage); } },
shouldLog(level) { const levels = { debug: 0, info: 1, warn: 2, error: 3 }; return levels[level] >= levels[logLevel]; },
getHistory() { return [...logHistory]; } }; })();
|
2. 模块工厂模式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56
| function createDatabase(config) { let connection = null; let isConnected = false;
return { async connect() { if (isConnected) { return connection; }
try { connection = await simulateConnection(config); isConnected = true; console.log('数据库连接成功'); return connection; } catch (error) { console.error('数据库连接失败:', error); throw error; } },
async disconnect() { if (connection) { await connection.close(); connection = null; isConnected = false; console.log('数据库连接已关闭'); } },
getStatus() { return { connected: isConnected, config: { ...config, password: '***' } }; } }; }
async function simulateConnection(config) { return new Promise((resolve) => { setTimeout(() => { resolve({ host: config.host, port: config.port, close: async () => console.log('连接已关闭') }); }, 100); }); }
module.exports = createDatabase;
|
(二)模块热重载
1. 开发环境热重载
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64
| const fs = require('fs'); const path = require('path');
class HotReloader { constructor() { this.watchers = new Map(); this.modules = new Map(); }
require(modulePath) { const fullPath = require.resolve(modulePath);
if (!this.watchers.has(fullPath)) { this.setupWatcher(fullPath); }
return require(modulePath); }
setupWatcher(filePath) { const watcher = fs.watchFile(filePath, (curr, prev) => { if (curr.mtime > prev.mtime) { console.log(`文件 ${filePath} 已更新,重新加载...`); this.reloadModule(filePath); } });
this.watchers.set(filePath, watcher); }
reloadModule(filePath) { delete require.cache[filePath];
this.emit('reload', filePath); }
emit(event, data) { if (this.listeners && this.listeners[event]) { this.listeners[event].forEach(callback => callback(data)); } }
on(event, callback) { if (!this.listeners) this.listeners = {}; if (!this.listeners[event]) this.listeners[event] = []; this.listeners[event].push(callback); }
cleanup() { this.watchers.forEach((watcher, filePath) => { fs.unwatchFile(filePath); }); this.watchers.clear(); } }
module.exports = new HotReloader();
|
(三)模块性能优化
1. 延迟加载
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| class LazyLoader { constructor() { this.cache = new Map(); }
createLazyGetter(target, property, modulePath) { Object.defineProperty(target, property, { get() { if (!this.cache.has(modulePath)) { console.log(`延迟加载模块: ${modulePath}`); this.cache.set(modulePath, require(modulePath)); } return this.cache.get(modulePath); }, configurable: true }); } }
const lazyLoader = new LazyLoader(); const services = {};
lazyLoader.createLazyGetter(services, 'imageProcessor', './heavy-image-processor'); lazyLoader.createLazyGetter(services, 'mlModel', './machine-learning-model'); lazyLoader.createLazyGetter(services, 'videoEncoder', './video-encoder');
module.exports = services;
|
2. 模块预加载
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
| class ModulePreloader { constructor() { this.preloadQueue = []; this.loaded = new Set(); }
add(modulePath, priority = 0) { this.preloadQueue.push({ modulePath, priority }); this.preloadQueue.sort((a, b) => b.priority - a.priority); }
async start() { console.log('开始预加载模块...');
for (const { modulePath } of this.preloadQueue) { if (!this.loaded.has(modulePath)) { try { await this.loadModule(modulePath); this.loaded.add(modulePath); } catch (error) { console.error(`预加载模块 ${modulePath} 失败:`, error); } } }
console.log('模块预加载完成'); }
async loadModule(modulePath) { return new Promise((resolve) => { setImmediate(() => { try { require(modulePath); resolve(); } catch (error) { resolve(); } }); }); } }
module.exports = new ModulePreloader();
|
七、常见问题与解决方案
(一)循环依赖问题
1. 识别循环依赖
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
| class DependencyTracker { constructor() { this.dependencies = new Map(); this.loading = new Set(); }
trackRequire(requirer, required) { if (!this.dependencies.has(requirer)) { this.dependencies.set(requirer, new Set()); } this.dependencies.get(requirer).add(required);
if (this.hasCycle(required, requirer)) { console.warn(`检测到循环依赖: ${requirer} <-> ${required}`); } }
hasCycle(start, target, visited = new Set()) { if (start === target) return true; if (visited.has(start)) return false;
visited.add(start); const deps = this.dependencies.get(start);
if (deps) { for (const dep of deps) { if (this.hasCycle(dep, target, visited)) { return true; } } }
return false; }
printDependencyGraph() { console.log('依赖关系图:'); for (const [module, deps] of this.dependencies) { console.log(`${module} -> [${Array.from(deps).join(', ')}]`); } } }
module.exports = new DependencyTracker();
|
2. 解决循环依赖
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
|
const orderService = require('./orderService');
class UserService { constructor() { this.getOrderService = () => orderService; }
getUserOrders(userId) { return this.getOrderService().getOrdersByUserId(userId); } }
module.exports = new UserService();
const EventEmitter = require('events'); module.exports = new EventEmitter();
const eventBus = require('./eventBus');
class UserService { createUser(userData) { const user = this.saveUser(userData); eventBus.emit('user:created', user); return user; } }
const eventBus = require('./eventBus');
eventBus.on('user:created', (user) => { console.log(`为用户 ${user.id} 初始化订单系统`); });
|
(二)模块路径问题
1. 路径解析工具
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52
| const path = require('path');
class PathResolver { constructor(baseDir = process.cwd()) { this.baseDir = baseDir; this.aliases = new Map(); }
setAlias(alias, realPath) { this.aliases.set(alias, path.resolve(this.baseDir, realPath)); }
resolve(modulePath) { for (const [alias, realPath] of this.aliases) { if (modulePath.startsWith(alias + '/')) { return modulePath.replace(alias, realPath); } }
if (modulePath.startsWith('./') || modulePath.startsWith('../')) { return path.resolve(this.baseDir, modulePath); }
return modulePath; }
createRequire(fromPath) { const resolver = this; return function customRequire(modulePath) { const resolvedPath = resolver.resolve(modulePath); return require(resolvedPath); }; } }
const resolver = new PathResolver(); resolver.setAlias('@lib', './lib'); resolver.setAlias('@utils', './utils'); resolver.setAlias('@config', './config');
const customRequire = resolver.createRequire(__dirname); const utils = customRequire('@utils/fileUtils');
module.exports = PathResolver;
|
八、总结
(一)CommonJS的优势
- 简单易用:语法简洁,学习成本低
- 同步加载:适合服务端环境,文件读取速度快
- 动态特性:支持条件加载和运行时模块解析
- 成熟稳定:在Node.js生态中经过长期验证
- 向后兼容:与大量现有npm包兼容
(二)使用建议
1. 最佳实践
- 模块职责单一:每个模块应该有明确的职责
- 避免循环依赖:通过合理的架构设计避免循环引用
- 合理使用缓存:利用模块缓存机制提高性能
- 错误处理:在require时进行适当的错误处理
- 文档完善:为模块提供清晰的API文档
2. 性能优化
- 延迟加载:对于大型模块使用延迟加载策略
- 模块预加载:在应用启动时预加载核心模块
- 缓存管理:在必要时清理模块缓存
- 路径优化:使用绝对路径或别名减少路径解析开销
(三)发展趋势
随着ES6模块的普及,CommonJS在前端开发中的使用逐渐减少,但在Node.js服务端开发中仍然占据重要地位。未来的发展趋势包括:
- 与ES6模块的互操作性:Node.js正在改进两种模块系统的兼容性
- 性能优化:持续优化模块加载和缓存机制
- 工具链支持:构建工具对CommonJS的支持将继续完善
- 渐进式迁移:现有项目可以逐步迁移到ES6模块
CommonJS作为JavaScript模块化的重要里程碑,为现代JavaScript开发奠定了坚实基础。理解和掌握CommonJS不仅有助于Node.js开发,也为学习其他模块系统提供了重要参考。
九、参考资料
官方文档
相关文章
学习资源
在线资源