欢迎光临
我们一直在努力

【JS】【前端】JavaScript 模块化演进之路(CJS/AMD/UMD/CMD/ESM)

CommonJS

CommonJS 是 JavaScript 在服务器端(Node.js)的一种 模块规范。

在浏览器之外的环境(比如服务器)中,JavaScript 需要一种方式来组织代码、管理依赖和导出功能。CommonJS 就是为了解决这个问题而诞生的。

核心概念

CommonJS 的精髓可以概括为三个关键词:require、module 和 exports

  • 导入(require): 使用 require() 函数来加载模块
  • 模块(module): 每个文件都是一个独立的模块,拥有自己的作用域
  • 导出(exports): 使用 module.exports 或 exports 对象来决定模块中哪些内容可以被外部访问

代码示例

假设我们有两个文件:

  • math.js (定义模块)
  • const add = (a, b) => a + b;
    const PI = 3.14;

    // 将需要公开的内容导出
    module.exports = {
    add,
    PI
    };

  • app.js (使用模块)
  • // 引入 math.js 模块
    const math = require('./math.js');

    console.log(math.add(2, 3)); // 输出 5
    console.log(math.PI); // 输出 3.14

    特点

    • 同步加载: 模块是按顺序加载的。这对服务器端(文件存在磁盘上)没问题,但由于网络延迟,这种方式不适合浏览器,这也是后来出现 ESM(ES Modules)的原因。
    • CJS 无法在浏览器中工作
    • 运行时执行: 只有代码运行到 require 这一行时,模块才会被加载和执行。
    • 值拷贝: 当你导出一个变量时,外部获取的是这个值的一个快照(副本)。即使原模块里的变量后来变了,外部拿到的值也不会跟着变。
    • 缓存: 模块第一次加载后会被缓存。再次 require 同一个模块时,会直接从缓存读,不会重新运行代码。

    AMD (Asynchronous Module Definition)

    在 CommonJS 统治服务器端的同时,浏览器端遇到了挑战:由于网络环境限制,脚本不能像在本地磁盘那样同步加载。AMD 应运而生,它采取了“异步加载”策略。

    特点

    • 核心实现: 最著名的库是 RequireJS
    • 语法特征: 使用 define 函数定义模块,使用 require 加载模块
    • AMD 异步导入模块(因此得名)
    • AMD 最初是为前端设计的(而 CJS 则用于后端)
    • 我认为 AMD 和 CJS 正好相反。

    示例

    // math.js – 定义模块
    define([], function() {
    const add = (a, b) => a + b;
    return { add };
    });

    // app.js – 使用模块
    require(['./math.js'], function(math) {
    console.log(math.add(2, 3)); // 输出 5
    });

    CMD (Common Module Definition)

    CMD 全称 Common Module Definition(通用模块定义),是国内开发者主导的规范,依托 SeaJS 实现,同样面向浏览器端。它和 AMD 的目标一致,但风格更灵活,主打 “按需加载”

    核心特点:就近依赖

    CMD 最大的亮点是 “就近依赖”—— 不用在模块开头就声明所有依赖,而是用到哪个模块,再在哪个位置用require加载,就像走到半路需要什么再临时取一样

    比如这样定义模块:

    define(function(require, exports, module) {
    // 用到时再加载依赖
    const dataService = require('./services/dataService');
    const uiUtils = require('./utils/uiUtils');

    function displayUserInfo() {
    const userData = dataService.fetchUserData();
    uiUtils.showMessage(userData.username);
    }

    exports.displayUserInfo = displayUserInfo;
    });

    这种方式的好处很明显:不用提前加载用不上的模块,减少初始加载资源;代码逻辑和依赖引入就近,读起来更顺,后续修改时改动范围也小,维护起来更轻松

    UMD (Universal Module Definition)

    随着模块化方案的割裂,开发者面临一个难题:我写的库是给 Node.js 用的(CommonJS),还是给浏览器用的?

    UMD 的核心是 “环境判断”—— 加载模块时,先判断当前运行环境,再选择对应的模块化规范:

    • 如果是 Node.js 环境,就用 CommonJS 的module.exports和require;
    • 如果是支持 AMD 的浏览器环境,就用define异步加载;
    • 如果是普通浏览器,就把模块挂载到window全局对象上

    示例

    (function (root, factory) {
    if (typeof define === 'function' && define.amd) {
    // AMD 环境
    define([], factory);
    } else if (typeof module === 'object' && module.exports) {
    // CommonJS 环境
    module.exports = factory();
    } else {
    // 浏览器全局环境
    root.myLibrary = factory();
    }
    }(typeof self !== 'undefined' ? self : this, function () {
    return { add: (a, b) => a + b };
    }));

    现在开发者很少手动写 UMD,但 Webpack 或 Rollup 等打包工具常生成 UMD 代码以确保最大的兼容性

    ES Modules

    ESM 是 ES Modules(ECMAScript Modules)的缩写。它是 JavaScript 官方在 2015 年(ES6)正式推出的模块化标准

    在 ESM 出现之前,JavaScript 社区一直使用 CommonJS(Node.js)或 AMD 等非官方方案。ESM 的诞生是为了让 JavaScript 拥有原生的、统一的模块系统,使其既能在浏览器中运行,也能在服务器端运行

    它不仅统一了标准,还带来了性能上的飞跃

    • 语法特征: import 和 export

    示例

    // math.js
    export const add = (a, b) => a + b;
    export const PI = 3.14;

    // app.js
    import { add, PI } from './math.js';
    console.log(add(2, 3));

    ESM 与 CommonJS 的区别对比

    这是面试和实践中最常被提及的部分:

  • 静态 vs 动态:
    • ESM 是静态的:import 必须在文件顶层。这让编译器可以在运行代码前知道模块结构,从而实现 Tree Shaking(剔除没用到的代码)。
    • CommonJS 是动态的:可以在 if 语句里写 require()。
  • 符号引用 (Live Bindings) vs 值拷贝:
    • CommonJS 导出的是值的副本。
    • ESM 导出的是值的引用。如果原模块里的变量变了,外部获取的值也会实时更新。
  • 加载方式:
    • ESM 原生支持异步加载,甚至可以支持 Top-level await。
  • 对比

    规范加载方式依赖处理适用场景核心语法示例
    CommonJS 同步加载 运行时确定,require同步获取 Node.js 服务器端开发 const mod = require('./mod'); module.exports = {}
    AMD 异步加载 依赖前置,定义时声明依赖 浏览器端开发,需兼容复杂依赖场景 define('mod', ['dep'], (dep) => {}); require(['mod'], (mod) => {})
    CMD 异步加载 就近依赖,用到时require加载 浏览器端开发,追求代码灵活简洁 define((require) => { const mod = require('./mod'); })
    UMD 环境判断,按需同步 / 异步 / 挂载全局 融合 CommonJS、AMD 特点 跨平台库 / 框架(浏览器、Node.js 通用) 见 示例的环境判断逻辑
    ESM 静态加载(编译阶段确定) import/export静态声明,只读 现代浏览器、Node.js 新项目,未来主流 import { mod } from './mod.js'; export const x = 1;

    站在 2026 年的节点看,ES Modules 已经是绝对的赢家。无论是现代浏览器、最新的 Node.js 版本,还是 Deno/Bun 等新兴运行时,都将 ESM 作为一等公民。AMD 已成往事,而UMD 隐身于工具链后

    参考

  • https://dev.to/iggredible/what-the-heck-are-cjs-amd-umd-and-esm-ikm
  • https://juejin.cn/post/7454030739781910537
  • 《JavaScript 高级程序设计》第5版
  • 赞(0)
    未经允许不得转载:171主机测评 » 【JS】【前端】JavaScript 模块化演进之路(CJS/AMD/UMD/CMD/ESM)
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址