Node.js 模块系统
章节介绍
Node.js 的模块系统是其核心特性之一,它让开发者能够将代码组织成可重用的模块。通过模块系统,我们可以将复杂的应用程序分解为多个小文件,每个文件负责特定的功能。本教程将详细介绍 Node.js 的模块系统,帮助您掌握模块化编程的核心概念。
核心知识点
模块系统概述
Node.js 使用 CommonJS 模块规范,每个 JavaScript 文件都被视为一个独立的模块。模块系统提供了以下功能:
- 代码封装:每个模块都有自己的作用域,不会污染全局命名空间
- 依赖管理:可以明确声明和加载依赖的模块
- 代码重用:通过导出和导入实现代码重用
- 可维护性:模块化使代码更易于维护和测试
模块系统工作流程:
┌─────────────┐
│ 主程序 │
│ (main.js) │
└──────┬──────┘
│ require()
↓
┌─────────────┐
│ 模块 A │
│ (moduleA.js)│
└──────┬──────┘
│ require()
↓
┌─────────────┐
│ 模块 B │
│ (moduleB.js)│
└─────────────┘require() 函数
require() 函数用于加载模块。
// 加载核心模块
const fs = require('fs');
const http = require('http');
const path = require('path');
// 加载文件模块
const myModule = require('./myModule.js');
const utils = require('./utils');
// 加载目录模块(会查找目录下的 index.js)
const myPackage = require('./myPackage');
// 加载 node_modules 中的第三方模块
const express = require('express');
const lodash = require('lodash');
// 加载 JSON 文件
const config = require('./config.json');
const package = require('./package.json');module.exports 和 exports
module.exports 和 exports 用于导出模块的内容。
// 方式 1:直接赋值给 module.exports
module.exports = {
name: '张三',
age: 25,
greet() {
console.log('你好!');
}
};
// 方式 2:使用 exports(注意:不能直接赋值)
exports.name = '李四';
exports.age = 30;
exports.greet = function() {
console.log('你好!');
};
// 方式 3:混合使用
exports.name = '王五';
exports.age = 28;
module.exports.greet = function() {
console.log('你好!');
};
// 方式 4:导出函数
module.exports = function(name) {
console.log(`你好,${name}!`);
};
// 方式 5:导出类
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
greet() {
console.log(`你好,我是 ${this.name}`);
}
}
module.exports = Person;模块导出方式对比
// math.js - 导出多个函数
exports.add = (a, b) => a + b;
exports.subtract = (a, b) => a - b;
exports.multiply = (a, b) => a * b;
exports.divide = (a, b) => a / b;
// 使用
const math = require('./math');
console.log(math.add(5, 3)); // 8
console.log(math.subtract(5, 3)); // 2
// utils.js - 导出单个对象
module.exports = {
formatDate(date) {
return date.toISOString();
},
capitalize(str) {
return str.charAt(0).toUpperCase() + str.slice(1);
}
};
// 使用
const utils = require('./utils');
console.log(utils.formatDate(new Date()));
console.log(utils.capitalize('hello'));
// logger.js - 导出类
class Logger {
constructor(name) {
this.name = name;
}
log(message) {
console.log(`[${this.name}] ${message}`);
}
error(message) {
console.error(`[${this.name}] ERROR: ${message}`);
}
}
module.exports = Logger;
// 使用
const Logger = require('./logger');
const logger = new Logger('App');
logger.log('应用启动');
logger.error('发生错误');模块查找机制
Node.js 按照以下顺序查找模块:
模块查找顺序:
1. 核心模块(fs, http, path 等)
↓
2. 相对路径(./ 或 ../)
↓
3. 绝对路径(/path/to/module)
↓
4. node_modules 目录
- 当前目录的 node_modules
- 父目录的 node_modules
- 继续向上查找,直到根目录
↓
5. 全局安装的模块// 1. 核心模块
const fs = require('fs');
// 2. 相对路径
const utils = require('./utils');
const helper = require('../helper/helper.js');
// 3. 绝对路径
const moduleA = require('/path/to/moduleA');
// 4. node_modules
const express = require('express');
// 5. 目录模块(查找 index.js)
const myPackage = require('./myPackage');
// 6. 不带扩展名(自动查找 .js, .json, .node)
const config = require('./config'); // 会查找 config.js, config.json, config.node模块缓存
Node.js 会缓存已加载的模块,提高性能。
// counter.js
let count = 0;
module.exports = {
increment() {
count++;
return count;
},
getCount() {
return count;
}
};
// app.js
const counter1 = require('./counter');
const counter2 = require('./counter');
console.log(counter1 === counter2); // true(同一个模块实例)
console.log(counter1.increment()); // 1
console.log(counter2.increment()); // 2
console.log(counter1.getCount()); // 2
console.log(counter2.getCount()); // 2
// 清除模块缓存
delete require.cache[require.resolve('./counter')];
const counter3 = require('./counter');
console.log(counter3.getCount()); // 0(新的模块实例)模块作用域
每个模块都有自己的作用域,变量不会污染全局。
// moduleA.js
const privateVar = '私有变量';
let counter = 0;
function privateFunction() {
console.log('私有函数');
}
module.exports = {
getCounter() {
return counter;
},
increment() {
counter++;
},
// 可以访问私有变量
logPrivate() {
console.log(privateVar);
privateFunction();
}
};
// app.js
const moduleA = require('./moduleA');
// console.log(privateVar); // 错误:无法访问私有变量
// privateFunction(); // 错误:无法访问私有函数
moduleA.increment();
console.log(moduleA.getCounter()); // 1
moduleA.logPrivate(); // 私有变量 \n 私有函数实用案例分析
案例 1:创建工具函数模块
创建一个实用的工具函数模块。
// utils/string.js
module.exports = {
capitalize(str) {
if (!str) return '';
return str.charAt(0).toUpperCase() + str.slice(1).toLowerCase();
},
truncate(str, length = 50) {
if (!str || str.length <= length) return str;
return str.slice(0, length) + '...';
},
reverse(str) {
if (!str) return '';
return str.split('').reverse().join('');
},
isEmail(str) {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return emailRegex.test(str);
},
isPhone(str) {
const phoneRegex = /^1[3-9]\d{9}$/;
return phoneRegex.test(str);
}
};
// utils/array.js
module.exports = {
unique(arr) {
return [...new Set(arr)];
},
chunk(arr, size) {
const chunks = [];
for (let i = 0; i < arr.length; i += size) {
chunks.push(arr.slice(i, i + size));
}
return chunks;
},
shuffle(arr) {
const shuffled = [...arr];
for (let i = shuffled.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[shuffled[i], shuffled[j]] = [shuffled[j], shuffled[i]];
}
return shuffled;
},
groupBy(arr, key) {
return arr.reduce((groups, item) => {
const groupKey = item[key];
if (!groups[groupKey]) {
groups[groupKey] = [];
}
groups[groupKey].push(item);
return groups;
}, {});
}
};
// utils/date.js
module.exports = {
format(date, format = 'YYYY-MM-DD HH:mm:ss') {
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0');
const hours = String(date.getHours()).padStart(2, '0');
const minutes = String(date.getMinutes()).padStart(2, '0');
const seconds = String(date.getSeconds()).padStart(2, '0');
return format
.replace('YYYY', year)
.replace('MM', month)
.replace('DD', day)
.replace('HH', hours)
.replace('mm', minutes)
.replace('ss', seconds);
},
addDays(date, days) {
const result = new Date(date);
result.setDate(result.getDate() + days);
return result;
},
diffDays(date1, date2) {
const oneDay = 24 * 60 * 60 * 1000;
return Math.round((date2 - date1) / oneDay);
},
isWeekend(date) {
const day = date.getDay();
return day === 0 || day === 6;
}
};
// utils/index.js
const stringUtils = require('./string');
const arrayUtils = require('./array');
const dateUtils = require('./date');
module.exports = {
...stringUtils,
...arrayUtils,
...dateUtils
};
// app.js
const utils = require('./utils');
console.log(utils.capitalize('hello')); // Hello
console.log(utils.truncate('This is a very long string', 10)); // This is a...
console.log(utils.unique([1, 2, 2, 3, 3, 3])); // [1, 2, 3]
console.log(utils.format(new Date())); // 2024-01-29 12:34:56案例 2:创建配置管理模块
创建一个配置管理模块,支持不同环境的配置。
// config/default.js
module.exports = {
app: {
name: 'My App',
version: '1.0.0',
port: 3000
},
database: {
host: 'localhost',
port: 5432,
name: 'myapp',
username: 'user',
password: 'password'
},
redis: {
host: 'localhost',
port: 6379,
db: 0
},
jwt: {
secret: 'your-secret-key',
expiresIn: '7d'
},
logging: {
level: 'info',
file: 'logs/app.log'
}
};
// config/development.js
module.exports = {
app: {
port: 3000
},
database: {
host: 'localhost',
name: 'myapp_dev'
},
logging: {
level: 'debug'
}
};
// config/production.js
module.exports = {
app: {
port: 80
},
database: {
host: 'prod-db.example.com',
name: 'myapp_prod'
},
redis: {
host: 'prod-redis.example.com'
},
logging: {
level: 'warn'
}
};
// config/index.js
const defaultConfig = require('./default');
const env = process.env.NODE_ENV || 'development';
try {
const envConfig = require(`./${env}`);
module.exports = {
...defaultConfig,
...envConfig
};
} catch (error) {
module.exports = defaultConfig;
}
// app.js
const config = require('./config');
console.log('应用配置:');
console.log(config);
console.log('\n数据库配置:');
console.log(config.database);
console.log('\n当前环境:', process.env.NODE_ENV || 'development');案例 3:创建数据库操作模块
创建一个数据库操作模块,封装常用的数据库操作。
// database/connection.js
const { Pool } = require('pg');
const config = require('../config');
class DatabaseConnection {
constructor() {
this.pool = new Pool({
host: config.database.host,
port: config.database.port,
database: config.database.name,
user: config.database.username,
password: config.database.password
});
}
async query(text, params) {
const client = await this.pool.connect();
try {
const result = await client.query(text, params);
return result.rows;
} finally {
client.release();
}
}
async transaction(callback) {
const client = await this.pool.connect();
try {
await client.query('BEGIN');
const result = await callback(client);
await client.query('COMMIT');
return result;
} catch (error) {
await client.query('ROLLBACK');
throw error;
} finally {
client.release();
}
}
async close() {
await this.pool.end();
}
}
module.exports = new DatabaseConnection();
// database/users.js
const db = require('./connection');
class UserRepository {
async create({ name, email, password }) {
const result = await db.query(
'INSERT INTO users (name, email, password) VALUES ($1, $2, $3) RETURNING *',
[name, email, password]
);
return result[0];
}
async findById(id) {
const result = await db.query(
'SELECT * FROM users WHERE id = $1',
[id]
);
return result[0];
}
async findByEmail(email) {
const result = await db.query(
'SELECT * FROM users WHERE email = $1',
[email]
);
return result[0];
}
async update(id, updates) {
const fields = Object.keys(updates);
const values = Object.values(updates);
const setClause = fields.map((field, index) => `${field} = $${index + 2}`).join(', ');
const result = await db.query(
`UPDATE users SET ${setClause} WHERE id = $1 RETURNING *`,
[id, ...values]
);
return result[0];
}
async delete(id) {
const result = await db.query(
'DELETE FROM users WHERE id = $1 RETURNING *',
[id]
);
return result[0];
}
async findAll(limit = 10, offset = 0) {
const result = await db.query(
'SELECT * FROM users ORDER BY created_at DESC LIMIT $1 OFFSET $2',
[limit, offset]
);
return result;
}
}
module.exports = new UserRepository();
// app.js
const userRepository = require('./database/users');
async function main() {
try {
// 创建用户
const user = await userRepository.create({
name: '张三',
email: 'zhangsan@example.com',
password: 'hashed_password'
});
console.log('创建用户:', user);
// 查找用户
const foundUser = await userRepository.findById(user.id);
console.log('查找用户:', foundUser);
// 更新用户
const updatedUser = await userRepository.update(user.id, {
name: '李四'
});
console.log('更新用户:', updatedUser);
// 删除用户
const deletedUser = await userRepository.delete(user.id);
console.log('删除用户:', deletedUser);
} catch (error) {
console.error('错误:', error);
}
}
main();代码示例
示例 1:创建计算器模块
// calculator.js
class Calculator {
add(a, b) {
return a + b;
}
subtract(a, b) {
return a - b;
}
multiply(a, b) {
return a * b;
}
divide(a, b) {
if (b === 0) {
throw new Error('除数不能为零');
}
return a / b;
}
power(base, exponent) {
return Math.pow(base, exponent);
}
sqrt(number) {
if (number < 0) {
throw new Error('不能计算负数的平方根');
}
return Math.sqrt(number);
}
}
module.exports = new Calculator();
// app.js
const calculator = require('./calculator');
console.log(calculator.add(5, 3)); // 8
console.log(calculator.subtract(5, 3)); // 2
console.log(calculator.multiply(5, 3)); // 15
console.log(calculator.divide(6, 3)); // 2
console.log(calculator.power(2, 3)); // 8
console.log(calculator.sqrt(16)); // 4示例 2:创建日志模块
// logger.js
const fs = require('fs');
const path = require('path');
class Logger {
constructor(name, options = {}) {
this.name = name;
this.level = options.level || 'info';
this.logFile = options.logFile || null;
this.levels = {
error: 0,
warn: 1,
info: 2,
debug: 3
};
}
formatMessage(level, message) {
const timestamp = new Date().toISOString();
return `[${timestamp}] [${this.name}] [${level.toUpperCase()}] ${message}`;
}
log(level, message) {
if (this.levels[level] > this.levels[this.level]) {
return;
}
const formattedMessage = this.formatMessage(level, message);
switch (level) {
case 'error':
console.error(formattedMessage);
break;
case 'warn':
console.warn(formattedMessage);
break;
case 'debug':
console.debug(formattedMessage);
break;
default:
console.log(formattedMessage);
}
if (this.logFile) {
this.writeToFile(formattedMessage);
}
}
writeToFile(message) {
fs.appendFileSync(this.logFile, message + '\n', 'utf8');
}
error(message) {
this.log('error', message);
}
warn(message) {
this.log('warn', message);
}
info(message) {
this.log('info', message);
}
debug(message) {
this.log('debug', message);
}
}
module.exports = Logger;
// app.js
const Logger = require('./logger');
const appLogger = new Logger('App', {
level: 'debug',
logFile: 'logs/app.log'
});
const dbLogger = new Logger('Database', {
level: 'warn',
logFile: 'logs/database.log'
});
appLogger.info('应用启动');
appLogger.debug('调试信息');
appLogger.warn('警告信息');
appLogger.error('错误信息');
dbLogger.info('数据库连接'); // 不会输出(level 是 warn)
dbLogger.warn('查询缓慢');
dbLogger.error('连接失败');示例 3:创建验证器模块
// validator.js
class Validator {
static isEmail(value) {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return emailRegex.test(value);
}
static isPhone(value) {
const phoneRegex = /^1[3-9]\d{9}$/;
return phoneRegex.test(value);
}
static isUrl(value) {
const urlRegex = /^https?:\/\/.+/;
return urlRegex.test(value);
}
static isNumber(value) {
return !isNaN(parseFloat(value)) && isFinite(value);
}
static isInteger(value) {
return Number.isInteger(Number(value));
}
static isPositive(value) {
return this.isNumber(value) && Number(value) > 0;
}
static isLength(value, min, max) {
const length = String(value).length;
return length >= min && length <= max;
}
static isRequired(value) {
return value !== null && value !== undefined && value !== '';
}
static isInRange(value, min, max) {
return this.isNumber(value) && value >= min && value <= max;
}
static matches(value, pattern) {
return pattern.test(value);
}
}
module.exports = Validator;
// app.js
const Validator = require('./validator');
console.log(Validator.isEmail('test@example.com')); // true
console.log(Validator.isEmail('invalid-email')); // false
console.log(Validator.isPhone('13800138000')); // true
console.log(Validator.isPhone('12345678901')); // false
console.log(Validator.isUrl('https://example.com')); // true
console.log(Validator.isUrl('example.com')); // false
console.log(Validator.isNumber('123')); // true
console.log(Validator.isNumber('abc')); // false
console.log(Validator.isInteger('123')); // true
console.log(Validator.isInteger('123.45')); // false
console.log(Validator.isPositive('10')); // true
console.log(Validator.isPositive('-5')); // false
console.log(Validator.isLength('hello', 3, 10)); // true
console.log(Validator.isLength('hi', 3, 10)); // false
console.log(Validator.isRequired('value')); // true
console.log(Validator.isRequired('')); // false
console.log(Validator.isInRange('5', 1, 10)); // true
console.log(Validator.isInRange('15', 1, 10)); // false实现技巧与注意事项
模块设计原则
- 单一职责:每个模块只负责一个功能
- 高内聚低耦合:模块内部功能相关,模块间依赖最小
- 清晰的接口:提供清晰的导出接口
- 文档完善:为模块提供使用文档
模块导出最佳实践
- 优先使用 module.exports:避免混淆 exports 和 module.exports
- 导出对象或类:而不是直接导出函数
- 保持一致性:统一使用一种导出方式
- 避免循环依赖:模块间不要相互依赖
模块加载优化
- 按需加载:只在需要时加载模块
- 利用缓存:Node.js 会缓存已加载的模块
- 避免重复加载:同一个模块只会被加载一次
- 使用相对路径:提高模块查找效率
常见问题与解决方案
问题 1:循环依赖
// moduleA.js
const moduleB = require('./moduleB');
module.exports = {
name: 'Module A',
greet() {
console.log('Hello from Module A');
moduleB.greet();
}
};
// moduleB.js
const moduleA = require('./moduleA');
module.exports = {
name: 'Module B',
greet() {
console.log('Hello from Module B');
moduleA.greet();
}
};
// app.js
const moduleA = require('./moduleA');
moduleA.greet(); // 可能导致错误或不完整的结果
// 解决方案:重构代码,避免循环依赖
// 或者延迟加载依赖问题 2:exports 和 module.exports 混淆
// 问题代码
exports.name = 'Module';
module.exports = {
greet() {
console.log('Hello');
}
};
// app.js
const myModule = require('./module');
console.log(myModule.name); // undefined(exports 被覆盖)
// 解决方案:只使用 module.exports
module.exports = {
name: 'Module',
greet() {
console.log('Hello');
}
};
// 或者只使用 exports
exports.name = 'Module';
exports.greet = function() {
console.log('Hello');
};问题 3:模块路径问题
// 问题代码
const utils = require('utils'); // 错误:没有指定路径
// 解决方案:使用相对路径
const utils = require('./utils');
// 或者使用绝对路径
const path = require('path');
const utils = require(path.join(__dirname, 'utils'));
// 或者将模块放在 node_modules 中
const utils = require('utils'); // 会查找 node_modules/utils问题 4:模块缓存导致的问题
// counter.js
let count = 0;
module.exports = {
increment() {
count++;
return count;
}
};
// app.js
const counter1 = require('./counter');
const counter2 = require('./counter');
counter1.increment(); // 1
counter2.increment(); // 2(同一个实例)
// 解决方案:如果需要独立实例,使用工厂函数
// counter.js
module.exports = function() {
let count = 0;
return {
increment() {
count++;
return count;
}
};
};
// app.js
const createCounter = require('./counter');
const counter1 = createCounter();
const counter2 = createCounter();
counter1.increment(); // 1
counter2.increment(); // 1(独立实例)总结
本教程详细介绍了 Node.js 的模块系统,包括 CommonJS 模块规范、require 和 module.exports 的使用方法、模块查找机制和模块缓存等重要内容。模块系统是 Node.js 编程的核心概念,掌握它对于开发可维护的 Node.js 应用至关重要。
通过本集的学习,您应该能够:
- 理解 Node.js 模块系统的工作原理
- 使用 require() 加载各种类型的模块
- 使用 module.exports 和 exports 导出模块
- 理解模块查找机制和缓存机制
- 创建可重用的模块
- 避免常见的模块使用错误
在下一集中,我们将学习 Node.js 的全局对象与全局变量,这是理解 Node.js 运行环境的重要一步。继续加油,您的 Node.js 技能正在不断提升!