Search K
Appearance
Appearance
📊 SEO元描述:2024年最新JavaScript CommonJS规范教程,详解Node.js模块系统、require和module.exports用法。包含完整代码示例和最佳实践,适合前端开发者快速掌握CommonJS模块化技术。
核心关键词:JavaScript CommonJS 2024、CommonJS规范、Node.js模块系统、require module.exports、JavaScript模块化规范
长尾关键词:CommonJS规范怎么使用、require和module.exports区别、Node.js模块化开发、CommonJS模块导入导出
通过本节JavaScript CommonJS规范详解,你将系统性掌握:
CommonJS是什么?这是服务端JavaScript开发者必须掌握的核心规范。CommonJS是为服务器端JavaScript制定的模块化规范,旨在解决JavaScript在服务器环境中的模块化问题,也是Node.js模块系统的基础。
💡 设计理念:CommonJS的目标是让JavaScript能够在任何地方运行,不仅仅是浏览器,为服务器端JavaScript提供标准化的模块系统
// 📈 CommonJS产生的背景
// 问题1:JavaScript缺乏模块系统
// 在CommonJS之前,JavaScript没有标准的模块系统
// 服务器端代码组织困难
// 问题2:全局污染
// 所有变量都在全局作用域中
var userName = 'John';
var userAge = 30;
function getUserInfo() {
return userName + ' is ' + userAge + ' years old';
}
// 问题3:依赖管理困难
// 无法明确表达模块间的依赖关系
// 加载顺序难以控制
// CommonJS解决方案
// 1. 每个文件是一个模块
// 2. 使用require()加载依赖
// 3. 使用module.exports导出接口
// 4. 提供模块缓存机制
// user.js
var name = 'John';
var age = 30;
function getUserInfo() {
return name + ' is ' + age + ' years old';
}
module.exports = {
getUserInfo: getUserInfo,
name: name,
age: age
};
// main.js
var user = require('./user');
console.log(user.getUserInfo()); // 'John is 30 years old'CommonJS的优势:
在Node.js中,每个JavaScript文件都被视为一个独立的模块:
// 🏗️ Node.js模块系统基础
// math.js - 数学工具模块
console.log('Loading math module...');
// 私有变量,外部无法直接访问
var PI = 3.14159;
var E = 2.71828;
// 私有函数
function validateNumber(num) {
return typeof num === 'number' && !isNaN(num);
}
// 公共函数
function add(a, b) {
if (!validateNumber(a) || !validateNumber(b)) {
throw new Error('Invalid number arguments');
}
return a + b;
}
function multiply(a, b) {
if (!validateNumber(a) || !validateNumber(b)) {
throw new Error('Invalid number arguments');
}
return a * b;
}
function getCircleArea(radius) {
if (!validateNumber(radius) || radius < 0) {
throw new Error('Invalid radius');
}
return PI * radius * radius;
}
// 导出公共接口
module.exports = {
add: add,
multiply: multiply,
getCircleArea: getCircleArea,
PI: PI,
E: E
};
// 或者使用ES6简写语法
// module.exports = { add, multiply, getCircleArea, PI, E };// 🔄 模块加载机制详解
// main.js
console.log('Starting main module...');
// 1. 加载自定义模块
var math = require('./math'); // 相对路径
console.log('Math module loaded');
// 2. 加载核心模块
var fs = require('fs'); // Node.js内置模块
var path = require('path');
// 3. 加载npm模块
var lodash = require('lodash'); // node_modules中的模块
// 使用加载的模块
console.log('2 + 3 =', math.add(2, 3));
console.log('Circle area (r=5):', math.getCircleArea(5));
console.log('PI value:', math.PI);
// 模块只会被加载一次
var math2 = require('./math'); // 不会重新执行math.js
console.log('Same module instance:', math === math2); // true
// 4. 动态加载模块
function loadModule(moduleName) {
try {
return require(moduleName);
} catch (error) {
console.error(`Failed to load module ${moduleName}:`, error.message);
return null;
}
}
var dynamicModule = loadModule('./optional-module');
if (dynamicModule) {
console.log('Optional module loaded successfully');
}Node.js使用特定的规则来解析模块路径:
// 🔍 模块解析规则详解
// 1. 核心模块(最高优先级)
var fs = require('fs'); // 加载Node.js内置的fs模块
var http = require('http'); // 加载Node.js内置的http模块
// 2. 文件模块
var myModule1 = require('./utils'); // 当前目录下的utils.js
var myModule2 = require('../lib/helper'); // 上级目录lib下的helper.js
var myModule3 = require('/absolute/path/module'); // 绝对路径
// 3. 目录模块
var dirModule = require('./my-package');
// Node.js会按以下顺序查找:
// 1. ./my-package/package.json 中的 main 字段
// 2. ./my-package/index.js
// 3. ./my-package/index.json
// 4. ./my-package/index.node
// 4. node_modules模块
var express = require('express');
// Node.js会从当前目录开始,逐级向上查找node_modules目录
// 模块解析示例
function demonstrateModuleResolution() {
console.log('Module resolution paths:');
console.log(require.resolve('fs')); // 核心模块路径
console.log(require.resolve('./math')); // 本地模块路径
// 显示模块搜索路径
console.log('Module paths:', module.paths);
}
demonstrateModuleResolution();
// 5. 文件扩展名处理
// Node.js会按以下顺序尝试扩展名:
// .js -> .json -> .node
var config1 = require('./config'); // 会查找 config.js
var config2 = require('./config.json'); // 直接加载 config.json// 📥 require函数深度解析
// 1. 基本语法
var module1 = require('module-name');
var module2 = require('./relative-path');
var module3 = require('/absolute-path');
// 2. 解构赋值(Node.js 6+)
var { add, multiply } = require('./math');
var { readFile, writeFile } = require('fs');
// 3. 重命名导入
var { add: mathAdd, multiply: mathMultiply } = require('./math');
// 4. 条件加载
var isDevelopment = process.env.NODE_ENV === 'development';
var logger = isDevelopment ? require('./dev-logger') : require('./prod-logger');
// 5. 动态加载
function requireSafely(modulePath) {
try {
return require(modulePath);
} catch (error) {
console.warn(`Module ${modulePath} not found:`, error.message);
return null;
}
}
// 使用示例
var optionalModule = requireSafely('./optional-feature');
if (optionalModule) {
optionalModule.initialize();
}// 🗄️ 模块缓存机制详解
// counter.js - 演示模块缓存
var count = 0;
function increment() {
return ++count;
}
function getCount() {
return count;
}
console.log('Counter module initialized, count:', count);
module.exports = {
increment: increment,
getCount: getCount
};
// main.js - 测试模块缓存
console.log('=== Module Caching Demo ===');
var counter1 = require('./counter');
console.log('First require - count:', counter1.getCount()); // 0
counter1.increment();
counter1.increment();
console.log('After increment - count:', counter1.getCount()); // 2
// 再次require同一个模块
var counter2 = require('./counter');
console.log('Second require - count:', counter2.getCount()); // 2 (缓存的结果)
console.log('Same instance?', counter1 === counter2); // true
// 查看模块缓存
console.log('Cached modules:');
Object.keys(require.cache).forEach(function(key) {
console.log(' -', key);
});
// 清除模块缓存(谨慎使用)
function clearModuleCache(modulePath) {
var resolvedPath = require.resolve(modulePath);
delete require.cache[resolvedPath];
}
// 清除缓存后重新加载
clearModuleCache('./counter');
var counter3 = require('./counter'); // 重新执行模块代码
console.log('After cache clear - count:', counter3.getCount()); // 0
// 热重载实现示例
function hotReload(modulePath) {
// 清除缓存
var resolvedPath = require.resolve(modulePath);
delete require.cache[resolvedPath];
// 重新加载
return require(modulePath);
}
// 监听文件变化并热重载(需要fs模块)
var fs = require('fs');
var path = require('path');
function watchAndReload(modulePath, callback) {
var resolvedPath = require.resolve(modulePath);
fs.watchFile(resolvedPath, function(curr, prev) {
console.log(`File ${modulePath} changed, reloading...`);
try {
var reloadedModule = hotReload(modulePath);
callback(null, reloadedModule);
} catch (error) {
callback(error, null);
}
});
}// 🚀 require高级用法
// 1. require.resolve - 获取模块路径
console.log('Module paths:');
console.log('fs module:', require.resolve('fs'));
console.log('local module:', require.resolve('./math'));
// 2. require.main - 判断是否为主模块
// utils.js
if (require.main === module) {
console.log('This file is being run directly');
// 执行测试代码
runTests();
} else {
console.log('This file is being required by another module');
}
function runTests() {
console.log('Running module tests...');
// 测试代码
}
module.exports = {
// 导出的功能
};
// 3. 模块包装器理解
// Node.js实际上将每个模块包装在一个函数中:
// (function(exports, require, module, __filename, __dirname) {
// // 模块代码
// });
// module-info.js - 演示模块包装器提供的变量
console.log('Module information:');
console.log('__filename:', __filename); // 当前文件的绝对路径
console.log('__dirname:', __dirname); // 当前目录的绝对路径
console.log('module.id:', module.id); // 模块标识符
console.log('module.filename:', module.filename); // 模块文件名
console.log('module.loaded:', module.loaded); // 模块是否已加载完成
console.log('module.parent:', module.parent); // 父模块
console.log('module.children:', module.children); // 子模块列表
// 4. 循环依赖处理
// a.js
console.log('a starting');
exports.done = false;
var b = require('./b.js');
console.log('in a, b.done =', b.done);
exports.done = true;
console.log('a done');
// b.js
console.log('b starting');
exports.done = false;
var a = require('./a.js');
console.log('in b, a.done =', a.done);
exports.done = true;
console.log('b done');
// main.js
console.log('main starting');
var a = require('./a.js');
var b = require('./b.js');
console.log('in main, a.done =', a.done, ', b.done =', b.done);
// 输出:
// main starting
// a starting
// b starting
// in b, a.done = false
// b done
// in a, b.done = true
// a done
// in main, a.done = true , b.done = true// 📤 module.exports详解
// 方式1:直接赋值对象
// calculator.js
module.exports = {
add: function(a, b) {
return a + b;
},
subtract: function(a, b) {
return a - b;
},
multiply: function(a, b) {
return a * b;
},
divide: function(a, b) {
if (b === 0) {
throw new Error('Division by zero');
}
return a / b;
}
};
// 方式2:逐个添加属性
// string-utils.js
module.exports.capitalize = function(str) {
return str.charAt(0).toUpperCase() + str.slice(1);
};
module.exports.reverse = function(str) {
return str.split('').reverse().join('');
};
module.exports.truncate = function(str, length) {
return str.length > length ? str.substring(0, length) + '...' : str;
};
// 方式3:导出单个函数
// logger.js
module.exports = function(message) {
var timestamp = new Date().toISOString();
console.log(`[${timestamp}] ${message}`);
};
// 方式4:导出类
// User.js
function User(name, email) {
this.name = name;
this.email = email;
this.createdAt = new Date();
}
User.prototype.getInfo = function() {
return `${this.name} (${this.email})`;
};
User.prototype.isActive = function() {
var daysSinceCreation = (Date.now() - this.createdAt.getTime()) / (1000 * 60 * 60 * 24);
return daysSinceCreation < 30; // 30天内创建的用户视为活跃
};
module.exports = User;
// 使用示例
// main.js
var calculator = require('./calculator');
var stringUtils = require('./string-utils');
var logger = require('./logger');
var User = require('./User');
// 使用计算器
console.log('2 + 3 =', calculator.add(2, 3));
console.log('10 - 4 =', calculator.subtract(10, 4));
// 使用字符串工具
console.log('Capitalized:', stringUtils.capitalize('hello world'));
console.log('Reversed:', stringUtils.reverse('hello'));
// 使用日志器
logger('Application started');
// 使用用户类
var user = new User('John Doe', 'john@example.com');
console.log('User info:', user.getInfo());
console.log('Is active:', user.isActive());// ⚖️ exports vs module.exports 详解
// 理解exports和module.exports的关系
// Node.js在模块开始时执行:var exports = module.exports = {};
// 正确用法1:使用exports添加属性
// good-exports.js
exports.name = 'MyModule';
exports.version = '1.0.0';
exports.greet = function(name) {
return `Hello, ${name}!`;
};
exports.config = {
debug: true,
timeout: 5000
};
// 正确用法2:使用module.exports
// good-module-exports.js
module.exports = {
name: 'MyModule',
version: '1.0.0',
greet: function(name) {
return `Hello, ${name}!`;
},
config: {
debug: true,
timeout: 5000
}
};
// 错误用法:重新赋值exports
// bad-exports.js
exports = { // ❌ 错误!这样做不会导出任何内容
name: 'MyModule',
greet: function(name) {
return `Hello, ${name}!`;
}
};
// 原因:exports只是module.exports的引用
// 重新赋值exports不会改变module.exports
// 正确的替代方案
// fixed-exports.js
module.exports = {
name: 'MyModule',
greet: function(name) {
return `Hello, ${name}!`;
}
};
// 混合使用的陷阱
// tricky-exports.js
exports.method1 = function() {
return 'method1';
};
// ❌ 这会覆盖之前通过exports添加的内容
module.exports = {
method2: function() {
return 'method2';
}
};
// 结果:只有method2会被导出,method1丢失
// 安全的混合使用方式
// safe-mixed-exports.js
// 先定义要导出的对象
var moduleExports = {
method2: function() {
return 'method2';
}
};
// 添加更多方法
moduleExports.method1 = function() {
return 'method1';
};
// 最后赋值给module.exports
module.exports = moduleExports;
// 或者使用Object.assign
module.exports = Object.assign({
method2: function() {
return 'method2';
}
}, {
method1: function() {
return 'method1';
}
});// 🎯 高级导出模式
// 1. 工厂模式导出
// database-factory.js
function createDatabase(config) {
var connection = null;
return {
connect: function() {
console.log(`Connecting to ${config.host}:${config.port}`);
connection = { connected: true };
return connection;
},
disconnect: function() {
if (connection) {
console.log('Disconnecting from database');
connection = null;
}
},
query: function(sql) {
if (!connection) {
throw new Error('Not connected to database');
}
console.log(`Executing query: ${sql}`);
return { rows: [] };
}
};
}
module.exports = createDatabase;
// 使用工厂模式
var createDB = require('./database-factory');
var db = createDB({ host: 'localhost', port: 5432 });
db.connect();
// 2. 单例模式导出
// config-singleton.js
var config = null;
function createConfig() {
if (config === null) {
config = {
environment: process.env.NODE_ENV || 'development',
port: process.env.PORT || 3000,
database: {
host: process.env.DB_HOST || 'localhost',
port: process.env.DB_PORT || 5432
},
get: function(key) {
return this[key];
},
set: function(key, value) {
this[key] = value;
}
};
console.log('Config singleton created');
}
return config;
}
module.exports = createConfig();
// 3. 命名空间导出
// api-namespace.js
var users = {
getAll: function() {
return [];
},
getById: function(id) {
return { id: id };
},
create: function(userData) {
return { id: Date.now(), ...userData };
}
};
var posts = {
getAll: function() {
return [];
},
getByUserId: function(userId) {
return [];
},
create: function(postData) {
return { id: Date.now(), ...postData };
}
};
module.exports = {
users: users,
posts: posts,
// 通用方法
utils: {
formatDate: function(date) {
return date.toISOString();
},
generateId: function() {
return Math.random().toString(36).substr(2, 9);
}
}
};
// 4. 条件导出
// environment-exports.js
var isDevelopment = process.env.NODE_ENV === 'development';
var isProduction = process.env.NODE_ENV === 'production';
var baseExports = {
version: '1.0.0',
log: function(message) {
console.log(message);
}
};
if (isDevelopment) {
// 开发环境特有的功能
baseExports.debug = function(message) {
console.log(`[DEBUG] ${message}`);
};
baseExports.inspect = function(obj) {
console.log(require('util').inspect(obj, { colors: true, depth: null }));
};
}
if (isProduction) {
// 生产环境特有的功能
baseExports.log = function(message) {
// 生产环境可能需要写入日志文件
require('fs').appendFileSync('app.log', `${new Date().toISOString()} ${message}\n`);
};
}
module.exports = baseExports;通过本节JavaScript CommonJS规范详解的学习,你已经掌握:
A: CommonJS是同步加载,运行时确定依赖;ES6模块是静态分析,编译时确定依赖。CommonJS使用require/module.exports,ES6使用import/export。
A: Node.js通过返回未完成的exports对象来处理循环依赖。最佳实践是重新设计模块结构,避免循环依赖的产生。
A: require.cache存储已加载的模块,避免重复执行模块代码。可以通过删除缓存来实现模块热重载,但要谨慎使用。
A: 当需要导出多个属性时使用exports,当需要导出单个对象、函数或类时使用module.exports。不要混合使用或重新赋值exports。
A: 不能直接使用,需要通过Browserify、Webpack等打包工具转换为浏览器可执行的代码。
// 问题:Cannot find module './my-module'
// 解决:检查文件路径和扩展名
// ❌ 错误的路径
var myModule = require('./my-module'); // 文件不存在
// ✅ 正确的解决方案
var path = require('path');
var fs = require('fs');
function requireSafely(modulePath) {
try {
// 检查文件是否存在
var resolvedPath = require.resolve(modulePath);
return require(modulePath);
} catch (error) {
console.error(`Module not found: ${modulePath}`);
console.error('Available files:', fs.readdirSync(__dirname));
return null;
}
}// 问题:循环依赖导致undefined
// 解决:重构模块结构或使用延迟加载
// ❌ 有问题的循环依赖
// a.js
var b = require('./b');
module.exports = { name: 'A', b: b };
// b.js
var a = require('./a'); // a可能还未完全加载
module.exports = { name: 'B', a: a };
// ✅ 解决方案:延迟加载
// a.js
module.exports = {
name: 'A',
getB: function() {
return require('./b'); // 延迟加载
}
};"掌握CommonJS规范是Node.js开发的基础,通过本节学习,你已经具备了服务端JavaScript模块化开发的核心技能。继续学习其他模块化规范,构建更完善的JavaScript模块化知识体系!"