CommonJS 和 ES6 Modules 到底有什么区别?

106 阅读4分钟

模块化简介

模块化编程是一种将代码分割成独立的、可重用的部分的方法。每个模块都有自己的作用域,并且可以导出一些功能供其他模块使用,同时也可以导入其他模块的功能。JavaScript 中主要有两种模块系统:CommonJSES6 Modules(ESM)

ESM 是静态模块系统,在编译/构建阶段就能确定哪些 export 被使用了,因此可以进行 Tree Shaking,把没用到的代码“摇掉”,最终打包体积更小,加载更快。

CommonJS 是动态模块系统,require() 是运行时函数调用,构建工具无法在编译时确定依赖关系,所以即使某个函数没被使用,整个模块依然会被完整打包进去。

1. CommonJS

CommonJS 是一种用于服务器端 JavaScript 的模块系统,主要用于 Node.js 环境。它通过 require 函数来导入模块,并使用 module.exportsexports 来导出内容。

导出单个函数/变量: 当你只需要从一个模块中导出一个单一的函数或变量时,可以使用 module.exports 直接导出该函数或变量。

// math.js
function add(a, b) {
    return a + b;
}

module.exports = add; // 直接导出一个函数

导出多个函数/变量: 如果你需要从一个模块中导出多个函数或变量,可以将它们放在一个对象中,然后使用 module.exports 导出整个对象。

// math.js
function add(a, b) { return a + b; }
function subtract(a, b) { return a - b; }

module.exports = {
    add: add,
    subtract: subtract
};
// 或者简写为:
module.exports = { add, subtract };

导入模块

解构导入:

解构导入允许你从模块中提取特定的函数或变量,并直接赋值给新的变量名。这种方式使得代码更加简洁和易读。

const { add, subtract } = require('./math');
console.log(add(2, 3)); // 输出 5
console.log(subtract(5, 2)); // 输出 3

在这个例子中,我们从 math.js 模块中解构导入了 addsubtract 函数,并可以直接使用它们。

整体导入:

整体导入意味着将整个模块作为一个对象导入。你可以通过这个对象访问模块中导出的所有函数和变量。

const math = require('./math');
console.log(math.add(2, 3)); // 输出 5
console.log(math.subtract(5, 2)); // 输出 3

在这个例子中,我们将 math.js 模块整体导入到 math 变量中,然后通过 math.addmath.subtract 访问其中的函数。

三、ES6 Modules (ESM)

ES6 Modules 是现代 JavaScript 的标准模块系统,支持在浏览器和服务器端使用。它通过 importexport 来实现模块的导入和导

导出模块

命名导出:

命名导出允许你从一个模块中导出多个函数、变量或类。每个导出都有一个名称,并且在导入时必须使用相同的名称(除非重命名)。

// math.js
export function add(a, b) {
    return a + b;
}

export function subtract(a, b) {
    return a - b;
}

默认导出:

默认导出允许你从一个模块中导出一个主要的值或函数。每个模块只能有一个默认导出,并且在导入时不需要使用花括号。

// math.js
export default function multiply(a, b) {
    return a * b;
}

或者使用箭头函数:

const multiply = (a, b) => a * b;
export default multiply;

导入模块

命名导入:

使用 import { ... } from '...' 语法可以从模块中导入命名导出的内容

import { add, subtract } from './math.js';

console.log(add(2, 3)); // 输出 5
console.log(subtract(5, 2)); // 输出 3

默认导入:

使用 import ... from '...' 语法可以从模块中导入默认导出的内容。不需要使用花括号。

import multiply from './math.js';

console.log(multiply(2, 3)); // 输出 6

混合导入:

可以同时导入默认导出和命名导出的内容。但是注意,默认导出必须写在前面,不然会报错。

import multiply, { add, subtract } from './math.js';

console.log(multiply(2, 3)); // 输出 6
console.log(add(2, 3)); // 输出 5
console.log(subtract(5, 2)); // 输出 3

重命名导入:

import { add as sumFunction } from './math.js';
console.log(sumFunction(2, 3)); // 输出 5

导入所有内容为对象:

使用 * as 语法可以将整个模块导入为一个对象。

import * as math from './math.js';
console.log(math.add(2, 3)); // 输出 5
console.log(math.subtract(5, 2)); // 输出 3

export与export default

Tree-shaking 只对 export(命名导出)有效,而对 export default 导出的对象无效,是因为 default 导出的是一个整体值(比如对象),无法静态分析其中哪些属性被用到了;而命名导出是“粒度清晰的绑定”,构建工具可以精确地知道哪个导出没被使用,从而剔除。

四、对比 CommonJS 和 ES6 Modules

特性CommonJSES6 Modules
语法require() / module.exportsimport / export
导入时机运行时加载编译时加载(静态分析)
默认导出不支持,默认需要自己模拟支持
命名导出需要手动打包到对象中直接支持
动态导入支持(如 require()支持(如 import()

五、总结

  • CommonJS 更适合于 Node.js 环境,提供了灵活的动态导入机制。
  • ES6 Modules 提供了更清晰、更现代化的语法,支持静态分析,适用于现代前端开发框架(如 React、Vue)以及现代浏览器环境。