CommonJS

CommonJS概述

CommonJS是Node.js使用的模块规范。

CommonJS特点

// CommonJS特点:
// 1. 同步加载模块
// 2. 运行时加载
// 3. 模块输出的是值的拷贝
// 4. this指向当前模块
// 5. 主要用于服务端

// 模块标识
// ./module    相对路径
// ../module   相对路径
// /module     绝对路径
// module      核心模块或node_modules

module.exports

导出模块内容。

基本导出

// 导出对象
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简写

// 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

require函数

导入模块内容。

基本导入

// 导入自定义模块
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

require特性

// 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];
});

与ES Modules对比

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后,让我们继续学习:

  1. [ES Modules](./59_ES Modules.md) - 学习ES模块
  2. 模块打包工具 - 学习打包工具
  3. 函数式编程基础 - 学习函数式编程

东巴文(db-w.cn) - 让编程学习更简单

🎯 东巴文寄语:CommonJS是Node.js的模块标准,理解其加载机制、缓存特性和与ES Modules的区别,对于Node.js开发至关重要。在 db-w.cn,我们帮你深入理解服务端模块化!