Skip to content

ESM(ECMAScript Modules)和 CommonJS 是两种 JavaScript 模块系统,它们用于在不同文件间组织和共享代码。两者有显著的区别,并且各自有优缺点。

一、ESM(ECMAScript Modules)

ESM 是 JavaScript 的原生模块系统,从 ECMAScript 6 (ES6) 开始引入。它在现代浏览器和 Node.js 中都受到支持。

1. 语法

  • 导出: 使用 export 来导出模块中的变量、函数、类等。

    js
    // 导出单个值
    export const foo = 'bar';
    
    // 导出多个值
    const a = 1, b = 2;
    export { a, b };
    
    // 默认导出
    export default function () {
      console.log('default export');
    }
  • 导入: 使用 import 来导入模块的内容。

    js
    // 导入模块
    import { a, b } from './module.js';
    
    // 导入默认导出
    import defaultFunction from './module.js';

2. 特点

  • 静态分析:ESM 在编译阶段解析,因此它支持静态分析(如 Tree Shaking,移除未使用的代码),有利于打包优化。
  • **作用域:**每个模块都有自己的独立作用域,导入的模块内容是只读的。
  • 顶层 await 支持:ESM 支持在模块的顶层使用 await,这在处理异步代码时非常有用。
  • 异步加载:浏览器环境中,ESM 支持按需异步加载模块,通过 <script type="module"> 标签实现。

3. 优点

  • 性能优化:由于支持静态分析,现代打包工具(如 Webpack、Rollup)可以对 ESM 进行 Tree Shaking,减少不必要的代码。
  • 浏览器原生支持:无需打包工具,直接在现代浏览器中使用。
  • 严格模式:ESM 模块默认使用严格模式(use strict),这能避免一些常见的 JavaScript 错误。

4. 缺点

  • 兼容性问题:虽然 ESM 在现代浏览器和 Node.js 中广泛支持,但在较旧的环境中可能不支持,需要通过工具(如 Babel)进行转换。
  • 异步加载:在 Node.js 中,ESM 的模块加载是异步的,相比于 CommonJS 的同步加载机制,这在某些场景下会带来额外的复杂性。

二、CommonJS

CommonJS 是最早为服务器端 JavaScript(如 Node.js)设计的模块系统,并成为 Node.js 的默认模块标准。

1. 语法

  • 导出: 使用 module.exports 来导出模块中的内容。

    js
    // 导出单个值
    module.exports = { foo: 'bar' };
    
    // 导出多个值
    exports.a = 1;
    exports.b = 2;
  • 导入: 使用 require 来导入模块。

    js
    const module = require('./module.js');
    const { a, b } = require('./module.js');

2. 特点

  • 同步加载:CommonJS 模块是同步加载的,这对服务器端来说是合适的,因为文件系统操作是本地的、快速的。
  • 单次加载、缓存机制:模块在首次加载时执行并缓存,后续的 require 调用会直接使用缓存结果,不会重复执行模块代码。
  • module.exportsexportsmodule.exports 是导出对象,而 exports 是对 module.exports 的引用。二者在导出单一内容时容易混淆。

3. 优点

  • Node.js 的默认模块系统:CommonJS 是 Node.js 的原生模块格式,使用广泛且兼容性好。
  • 简单的同步加载:模块同步加载,适用于服务器端的快速读取文件系统的场景。

4. 缺点

  • 无法进行静态分析:由于 CommonJS 的模块系统是动态的,导入的内容只有在运行时才能确定,因此打包工具无法进行 Tree Shaking 等静态优化。
  • 浏览器不支持:浏览器不能原生运行 CommonJS 模块,必须通过打包工具将其转换为 ESM 或其他格式。
  • 模块之间依赖不明确:因为 require 是动态的,难以在编译阶段分析模块之间的依赖关系。

三、区别总结

特点ESMCommonJS
引入方式importrequire
导出方式export / export defaultmodule.exports / exports
加载方式静态、异步加载动态、同步加载
是否支持静态分析支持,编译时可分析依赖关系和导入内容不支持,只能在运行时确定
模块作用域独立作用域,导入的内容是只读的独立作用域,但导入的内容可以修改
缓存机制有,导入后缓存有,首次加载后缓存,不会重复执行
顶层 await支持不支持
适用场景现代前端开发、浏览器和 Node.js 环境Node.js 服务端开发

四、选择建议

  1. 在 Node.js 中:

    • 如果你的项目是现代的,并且可以使用 Node.js v12+,建议使用 ESM,因为它是未来的标准,支持更好的优化和异步功能。
    • 如果你的项目依赖于较旧的模块系统或工具链,可以继续使用 CommonJS,尤其是当你需要与大量 CommonJS 模块集成时。
  2. 在前端开发中:

    • ESM 是浏览器原生支持的模块系统,适合用于现代前端项目,并且与打包工具(如 Webpack、Rollup)结合时可以发挥更好的性能优化。