CommonJS是Node.js使用的模块规范。
// CommonJS特点:
// 1. 同步加载模块
// 2. 运行时加载
// 3. 模块输出的是值的拷贝
// 4. this指向当前模块
// 5. 主要用于服务端
// 模块标识
// ./module 相对路径
// ../module 相对路径
// /module 绝对路径
// module 核心模块或node_modules
导出模块内容。
// 导出对象
module.exports = {
name: "东巴文",
greet: function() {
return "Hello";
}
};
// 导出函数
module.exports = function(name) {
return `Hello, ${name}`;
};
// 导出类
module.exports = class User {
constructor(name) {
this.name = name;
}
};
// 导出单个值
module.exports = "东巴文";
// exports是module.exports的引用
exports.name = "东巴文";
exports.age = 25;
exports.greet = function() {
return "Hello";
};
// 等价于
module.exports.name = "东巴文";
module.exports.age = 25;
module.exports.greet = function() {
return "Hello";
};
// 注意:不能直接给exports赋值
// exports = { name: "东巴文" }; // 错误!断开了引用
// 方式1:添加属性
exports.a = 1;
exports.b = 2;
// module.exports = { a: 1, b: 2 }
// 方式2:整体赋值
module.exports = { a: 1, b: 2 };
// 方式3:混合使用
exports.a = 1;
module.exports = { b: 2 };
// 最终导出 { b: 2 }(后者覆盖)
// 推荐:统一使用module.exports
导入模块内容。
// 导入自定义模块
const user = require("./user");
console.log(user.name);
// 导入核心模块
const fs = require("fs");
const path = require("path");
const http = require("http");
// 导入node_modules
const lodash = require("lodash");
const express = require("express");
// 1. 核心模块
require("fs"); // 直接使用内置模块
// 2. 相对/绝对路径
require("./user"); // ./user.js
require("./user.js"); // ./user.js
require("./user.json"); // ./user.json
require("./user/index"); // ./user/index.js
// 3. node_modules
require("lodash"); //
// 查找顺序:
// ./node_modules/lodash
// ../node_modules/lodash
// ../../node_modules/lodash
// 直到根目录
// 4. 目录
require("./dir");
// 查找:
// ./dir/package.json (main字段)
// ./dir/index.js
// ./dir/index.json
// 1. 模块缓存
const a = require("./module");
const b = require("./module");
console.log(a === b); // true(同一实例)
// 2. 只执行一次
// module.js
console.log("模块加载");
module.exports = { value: 1 };
// 多次require只执行一次
require("./module"); // 输出:模块加载
require("./module"); // 不输出
// 3. 循环依赖
// a.js
const b = require("./b");
module.exports = { a: 1 };
// b.js
const a = require("./a"); // 得到a的部分导出
module.exports = { b: 2 };
CommonJS模块的作用域特性。
// 每个模块都有以下变量
console.log(__dirname); // 模块所在目录
console.log(__filename); // 模块文件完整路径
console.log(module.id); // 模块标识符
console.log(module.filename); // 模块文件名
console.log(module.loaded); // 是否已加载
console.log(module.parent); // 父模块
console.log(module.children); // 子模块
console.log(module.paths); // 模块查找路径
console.log(exports); // 导出对象
console.log(require.main); // 入口模块
// 每个模块有独立的作用域
// moduleA.js
var name = "东巴文";
// moduleB.js
var name = "db-w.cn"; // 不冲突
// app.js
require("./moduleA");
require("./moduleB");
// 两个name变量互不影响
// this指向模块
console.log(this === module.exports); // true
console.log(this === exports); // true
CommonJS的加载原理。
// 1. 解析路径
// 2. 检查缓存
// 3. 创建模块对象
// 4. 执行模块代码
// 5. 缓存模块
// 6. 返回exports
// 手动实现require
function myRequire(modulePath) {
// 1. 解析绝对路径
const absolutePath = path.resolve(__dirname, modulePath);
// 2. 检查缓存
if (require.cache[absolutePath]) {
return require.cache[absolutePath].exports;
}
// 3. 创建模块
const module = {
exports: {},
id: absolutePath,
loaded: false
};
// 4. 缓存模块
require.cache[absolutePath] = module;
// 5. 执行代码
const code = fs.readFileSync(absolutePath, "utf8");
const wrapper = new Function("exports", "require", "module", "__dirname", "__filename", code);
wrapper.call(module.exports, module.exports, myRequire, module, path.dirname(absolutePath), absolutePath);
// 6. 标记已加载
module.loaded = true;
return module.exports;
}
// 模块只加载一次
// 第一次require执行代码
// 后续require从缓存读取
// 查看缓存
console.log(require.cache);
// 删除缓存(重新加载)
delete require.cache[require.resolve("./module")];
// 清除所有缓存
Object.keys(require.cache).forEach(key => {
delete require.cache[key];
});
CommonJS与ES Modules的区别。
// CommonJS
// 1. 运行时加载
// 2. 输出值拷贝
// 3. 同步加载
// 4. this指向模块
// ES Modules
// 1. 编译时加载
// 2. 输出值引用
// 3. 异步加载
// 4. this指向undefined
// 值拷贝 vs 值引用
// cjs.js
let count = 0;
module.exports = {
count,
increment() {
count++;
}
};
// 使用
const counter = require("./cjs");
console.log(counter.count); // 0
counter.increment();
console.log(counter.count); // 0(值拷贝,不变)
// esm.js
export let count = 0;
export function increment() {
count++;
}
// 使用
import { count, increment } from "./esm.js";
console.log(count); // 0
increment();
console.log(count); // 1(值引用,变)
// Node.js中ES Modules导入CommonJS
// cjs.cjs
module.exports = { name: "东巴文" };
// esm.mjs
import cjs from "./cjs.cjs";
console.log(cjs.name); // 东巴文
// CommonJS导入ES Modules
// esm.mjs
export const name = "东巴文";
// cjs.cjs
const esm = require("./esm.mjs"); // 错误!不支持
// 使用动态导入
async function load() {
const esm = await import("./esm.mjs");
console.log(esm.name);
}
CommonJS的实际使用场景。
// utils.js
const formatDate = (date) => {
return date.toISOString().split("T")[0];
};
const debounce = (fn, delay) => {
let timer;
return (...args) => {
clearTimeout(timer);
timer = setTimeout(() => fn(...args), delay);
};
};
const deepClone = (obj) => {
return JSON.parse(JSON.stringify(obj));
};
module.exports = {
formatDate,
debounce,
deepClone
};
// config.js
const env = process.env.NODE_ENV || "development";
const config = {
development: {
apiUrl: "http://localhost:3000",
debug: true
},
production: {
apiUrl: "https://api.db-w.cn",
debug: false
}
};
module.exports = config[env];
// database.js
const mysql = require("mysql2/promise");
const pool = mysql.createPool({
host: "localhost",
user: "root",
password: "password",
database: "mydb"
});
module.exports = {
async query(sql, params) {
const [rows] = await pool.execute(sql, params);
return rows;
},
async insert(table, data) {
const keys = Object.keys(data);
const values = Object.values(data);
const sql = `INSERT INTO ${table} (${keys.join(", ")}) VALUES (${keys.map(() => "?").join(", ")})`;
const [result] = await pool.execute(sql, values);
return result.insertId;
}
};
掌握了CommonJS后,让我们继续学习:
东巴文(db-w.cn) - 让编程学习更简单
🎯 东巴文寄语:CommonJS是Node.js的模块标准,理解其加载机制、缓存特性和与ES Modules的区别,对于Node.js开发至关重要。在 db-w.cn,我们帮你深入理解服务端模块化!