JavaScriptNodeJs

CommonJS 和 ES Module

为什么需要模块🔖


可以把 CommonJS 和 ES6 Module(简称 ESM)理解成 JavaScript 代码拆分成多个文件后,不同的‘文件之间互相调用’的规则 —— 就像你写作文时,把内容拆成几个笔记本,需要时从其他笔记本里 “抄内容” 的不同规矩。

🔹先搞懂 “为什么需要模块”?

假设你写了 1000 行代码,如果全堆在一个文件里,又乱又难维护。所以要 拆分文件:比如把 “工具函数” 放 utils.js,“业务逻辑” 放 app.js

但拆分后,app.js 怎么用 utils.js 里的函数?这就需要 “模块规则” 来规定:

  • 怎么把 utils.js 里的内容 “暴露出去”?
  • 怎么在 app.js 里 “拿到这些内容”?

  

🔹CommonJS:Node.js 发明的 “老规矩”

CommonJS 是 Node.js 早期为了拆分代码搞的规则(前端后来也用,但需要工具转译),核心是 2 个操作:

1. 暴露内容:module.exports

比如在 utils.js 里写工具函数,用 module.exports 把函数 “交出去”:

// utils.js(CommonJS 写法)
// 定义一个工具函数
function add(a, b) {
  return a + b;
}
// 把 add 函数暴露出去,让其他文件能用
module.exports = add;

// 或者暴露多个内容(用对象)
module.exports = {
  add: add,
  subtract: function(a, b) { return a - b; }
};

2. 调用内容:require()

app.js 里用 require()utils.js 的内容 “拿过来”:

// app.js(CommonJS 写法)
// 拿到 utils.js 暴露的内容
const utils = require('./utils.js');

// 用里面的函数
console.log(utils.add(1, 2)); // 输出 3
console.log(utils.subtract(5, 3)); // 输出 2

  

🔹CommonJS 的特点

  • 像 “临时借东西”:代码执行到 require() 这一行,才会去读 utils.js 文件、执行里面的代码,然后拿到内容。
  • 不用写全文件后缀:require('./utils') 会自动找 utils.js(懒人福利)。
  • 只在 Node.js 里能用:浏览器不认识 require,得用 Webpack 这类工具 “翻译” 成浏览器能懂的代码。

  

🔹ES6 Module:JavaScript 官方的 “新规矩”

ES6 Module 是 JavaScript 语言官方定的规则(现在前端 / Node.js 都在用),核心也是 2 个操作:

1. 暴露内容:export

同样在 utils.js 里,用 export 暴露内容:

// utils.js(ESM 写法)
// 暴露单个内容(默认暴露)
export default function add(a, b) {
  return a + b;
}

// 暴露多个内容(命名暴露)
export function subtract(a, b) {
  return a - b;
}
export const PI = 3.14;

2. 调用内容:import

app.js 里用 import 拿内容:

// app.js(ESM 写法)
// 拿“默认暴露”的内容
import add from './utils.js';
// 拿“命名暴露”的内容(用大括号)
import { subtract, PI } from './utils.js';

// 用这些内容
console.log(add(1,2)); // 3
console.log(subtract(5,3)); // 2
console.log(PI); // 3.14

  

🔹ES6 Module 的特点(通俗版)

  • 像 “提前把所有东西准备好”:代码还没执行,先把所有 import 的文件都读一遍、解析好(比如 app.js 开头的 import,会先加载 utils.js)。
  • 必须写全文件后缀:import ... from './utils.js' 不能省略 .js(官方规矩,严谨但有点麻烦)。
  • 哪里都能用:现代浏览器直接支持(不用工具),Node.js 也支持(只要在 package.json 里加 "type": "module")。

  

🔹一句话总结区别

  

CommonJS 和 ES6 Module 的背景和作用🔖


CommonJS 和 ES6 Module(也称为 ECMAScript 模块)是 JavaScript 中两种不同的模块系统,它们的出现都是为了解决 JavaScript 模块化编程的问题。

CommonJS一种早期的模块系统,它在 Node.js 中得到了广泛应用。CommonJS 采用了导出和导入的方式来实现模块之间的依赖关系管理。在 CommonJS 中,每个模块都有一个单独的文件,模块中的内容通过 module.exports 对象导出,其他模块可以通过 require() 函数来导入所需的模块。

CommonJS 的作用是为了解决 JavaScript 中模块化编程的问题,它提供了一种简单而直观的方式来组织和管理代码。通过使用 CommonJS,开发者可以更好地复用代码,提高代码的可维护性和可读性。

ES6 Module 是 JavaScript 的原生模块系统,它是在 ECMAScript 2015 及以后的版本中引入的。ES6 Module 采用了新的语法和语义,它允许在代码中直接声明和导入导出模块。在 ES6 Module 中,每个文件都是一个独立的模块,通过 exportimport 关键字来声明和导入导出内容。

ES6 Module 的作用是提供了一种更简洁、更现代化的模块化编程方式。它解决了 CommonJS 中一些问题,如循环依赖和命名空间污染。ES6 Module 还支持模块的静态分析和编译时优化,提高了代码的性能和可维护性。

总的来说,CommonJS 和 ES6 Module 都是为了解决 JavaScript 中的模块化编程问题而出现的。CommonJS 是一种早期的解决方案,而 ES6 Module 是 JavaScript 的原生模块系统,提供了更现代化和简洁的方式来管理模块。在实际开发中,可以根据项目的需求和环境选择使用哪种模块系统。

  

🔹ES6 Module 就是 ES module吗?

你问的这个问题特别关键,答案很明确:ES6 Module 就是 ES Module(ESM),二者是同一个东西的不同叫法,只是缩写 / 简称的差异。

先通俗解释:名字的由来

  • ES6 Module:因为这个模块规范是在 ES6(ECMAScript 2015) 版本中正式被纳入 JavaScript 官方标准的,所以最初大家习惯叫 “ES6 模块”,突出它的版本来源;
  • ES Module(简称 ESM):是更通用、更简洁的叫法(去掉 “6”),因为后续 ES7、ES8 等版本并没有修改这个模块规范,它已经成为 JavaScript 官方 “模块系统” 的统称,现在行业里更常用 ES ModuleESM 来指代。

  

CommonJS 和 ES Module 的通俗理解🔖


CommonJS 和 ESM 是 JavaScript 中两种不同的模块规范(简单说就是 “代码怎么拆分、怎么导入导出” 的规则),可以理解成 “两个不同的‘代码积木拼接方式’”。

🔹先通俗理解核心区别

想象你要把代码拆成多个文件(比如把 “工具函数” 放一个文件,“业务逻辑” 放另一个):

  • CommonJS:是 Node.js 早期发明的 “积木拼接规则”,用 require() 拿别人的积木,用 module.exports 把自己的积木给别人。
  • ESM:是 JavaScript 官方制定的 “积木拼接规则”,用 import 拿别人的积木,用 export 把自己的积木给别人。

  

🔹详细对比(通俗版)

1. 语法不同(最直观)

操作CommonJS(Node.js 老规则)ESM(官方新规则)
导入模块const A = require('./a.js')import A from './a.js'
导出单个内容module.exports = 123export default 123
导出多个内容module.exports = { a:1, b:2 }export const a=1; export const b=2
导入多个内容const {a,b} = require('./a.js')import {a,b} from './a.js'

2. 运行时机不同(关键区别)

  • CommonJS:运行时加载(代码执行到 require() 这一行,才去读文件、执行代码)。
// 先执行其他代码
console.log('先打印这个');
// 执行到这一行,才去加载 a.js
const A = require('./a.js');
  • ESM:编译时加载(代码还没执行,先把所有 import 的文件都读一遍、解析好)。
// 必须先写 import,不能写在 console 后面
import A from './a.js';
console.log('后打印这个');

3. 环境支持不同

  • CommonJS:
    • 天生支持:Node.js(所有版本)。
    • 前端不支持:浏览器默认不认识 require,需要 Webpack、Browserify 等工具转译。
  • ESM:
    • 现代支持:Node.js(v12+,需要配置 "type": "module")、所有现代浏览器(直接支持 import)。
    • 是官方标准:未来 JavaScript 模块的统一规范。

  

CommonJS 的基本概念和工作原理🔖


CommonJS 是一种早期的模块系统,它在 Node.js 中得到了广泛应用。它采用了导出和导入的方式来实现模块之间的依赖关系管理。在 CommonJS 中,每个模块都有一个单独的文件,模块中的内容通过module.exports对象导出,其他模块可以通过require()函数来导入所需的模块。

🔹描述 CommonJS 的模块导出和导入方式

在CommonJS中,模块通过module.exports对象导出,其他模块可以通过require()函数来导入所需的模块。下面是模块导出和导入的示例代码:

模块导出:

let version = 1.0;
const sayHi = name => `您好, ${name}`;
module.exports.version = version;
module.exports.sayHi = sayHi;

模块导入:

let a = require("./b.js");
console.log(a.version);
console.log(a.sayHi("FELaoL"));

在导入模块时,后缀可以省略。require()函数会返回导出模块的内容,并将其赋值给导入模块中的变量。

  

🔹探讨 CommonJS 的优缺点

  • CommonJS 的优点包括:
    • 简单直观:CommonJS 的模块导出和导入方式相对简单,容易理解和使用。
    • 向后兼容:CommonJS 是一种比较古老的模块规范,它与传统的 JavaScript 代码风格兼容,使得迁移到模块化开发更加容易。
    • 适合服务器端:CommonJS 在 Node.js 环境中得到广泛应用,因为它适合服务器端的模块开发。
  • CommonJS 的缺点包括:
    • 模块作用域:CommonJS 采用全局模块作用域,意味着所有模块中的变量和函数都是全局的,可能导致命名冲突。
    • 循环依赖问题:CommonJS 不支持循环依赖,即模块 A 依赖模块 B,而模块 B 又依赖模块 A。这在一些情况下可能会导致问题。
    • 语法相对繁琐:与 ES6 模块相比,CommonJS 的导出和导入语法相对繁琐,需要使用module.exportsrequire()

总体而言,CommonJS 是一种简单且向后兼容的模块规范,但在一些方面可能不如 ES6 模块那么现代化和灵活。在实际开发中,可以根据项目的需求和环境选择使用哪种模块系统。

  

CommonJS 的基本概念和工作原理🔖


ES6 Module(也称为 ECMAScript 模块)是 JavaScript 的一种模块化编程的规范,它是在 ECMAScript 2015 及以后的版本中引入的。ES6 Module 采用了新的语法和语义,允许在代码中直接声明和导入导出模块。

ES6 Module 的主要特点包括:

  1. 模块声明:使用exportimport关键字来声明和导入导出内容。
  2. 模块导出:通过export关键字来导出模块中的变量、函数、类等,以便其他模块可以导入和使用它们。
  3. 模块导入:使用import关键字来导入其他模块中导出的内容,可以指定导入的内容以及使用别名。
  4. 模块的静态分析:ES6 Module 支持模块的静态分析,意味着在编译时可以确定模块之间的依赖关系,避免了动态加载时可能出现的问题。
  5. 模块的命名空间:每个模块都有自己的独立命名空间,避免了命名冲突。

ES6 Module 是 JavaScript 的原生模块系统,提供了一种更简洁、更现代化的模块化编程方式。它解决了 CommonJS 中一些问题,如循环依赖和命名空间污染。ES6 Module 还支持模块的静态分析和编译时优化,提高了代码的性能和可维护性。

需要注意的是,ES6 Module 需要支持的 JavaScript 环境才能运行。一些旧的浏览器或环境可能需要额外的 polyfill 或转换工具来支持 ES6 Module 的语法。

  

🔹描述 ES6 Module 的导出和导入方式

ES6 Module 的导出方式有以下几种:

  • ES6 Module 的导出方式有以下几种:
    • export const name = 'value'; 命名导出,将模块中的变量、函数或类等以指定名称导出。
    • export default 'value'; 默认导出,不需要任何名称,将模块中的默认值导出。
    • export { name1, name2 as newName2 } 导出列表,将多个变量、函数或类等以指定名称导出,并且可以为其中一个或多个重命名。
  • ES6 Module 的导入方式有以下几种:
    • import { name } from ‘some-path/file’; 命名导入,将其他模块中以指定名称导出的变量、函数或类等导入。
    • import anyName from ‘some-path/file’; 默认导入,将其他模块中默认导出的变量、函数或类等导入,不需要指定名称。
    • import * as anyName from ‘some-path/file’; 命名+默认导入,将其他模块中以指定名称导出的变量、函数或类等导入,并将它们放入一个命名空间中。

你可以根据实际需求选择合适的导出和导入方式。在使用时,需要注意模块的相对路径和命名空间的使用,以确保正确导入和使用模块中的内容。

  

🔹探讨 ES6 Module 的优缺点

  • ES6 Module(也称为 ECMAScript 模块)是 JavaScript 的一种模块化编程的规范,它具有以下优点:
    • 更好的代码组织和可读性:ES6 Module 允许将代码分割成多个独立的文件,每个文件都是一个模块。这样可以更好地组织代码,提高可读性和维护性。
    • 避免命名冲突:每个模块都有自己的独立命名空间,避免了全局命名空间中的命名冲突问题。
    • 支持模块的静态分析:ES6 Module 支持在编译时进行静态分析,这有助于提前发现潜在的问题,提高代码的可靠性。
    • 更好的性能:由于模块是静态的,在编译时可以进行优化,从而提高代码的运行性能。
    • 支持模块的按需加载:可以通过import()动态地加载模块,只加载需要的模块,减少初始加载时间。
  • 然而,ES6 Module 也有一些缺点:
    • 兼容性问题:ES6 Module 需要支持的 JavaScript 环境才能运行,一些旧的浏览器或环境可能需要额外的 polyfill 或转换工具来支持 ES6 Module 的语法。
    • 学习曲线:ES6 Module 引入了新的语法和语义,对于一些开发者来说可能需要一定的学习成本。
    • 调试困难:在调试时,由于模块的导入和导出都是在编译时进行的,所以在调试过程中可能会遇到一些挑战。

总体而言,ES6 Module 是 JavaScript 模块化编程的重要进展,它提供了更好的代码组织和可读性,解决了命名冲突等问题。尽管存在一些兼容性和学习成本的问题,但随着时间的推移,这些问题会逐渐得到解决,ES6 Module 也将成为 JavaScript 开发的标准方式。

  

CommonJS 和 ES6 Module 的区别🔖


🔹比较 CommonJS 和 ES6 Module 在语法和语义上的差异

下面是对 CommonJS 和 ES6 Module 在语法和语义上的差异进行详细比较的表格:


CommonJS
ES6 Module
导出方式使用 module.exports 和 exports使用 export
导入方式使用 require使用 import
动态导入不支持支持动态导入,可以在运行时动态加载模块
默认导出使用 module.exports 或 exports使用 export default
命名空间不支持不支持命名空间,模块间的导出和引入是直接的一对一映射
静态分析不支持支持静态分析,可以在构建时进行依赖分析和优化
对循环依赖的处理使用同步的导入方式处理循环依赖使用异步的导入方式处理循环依赖并保持引用关系
动态导出不支持不支持动态导出,模块的导出是静态定义的

需要注意的是,CommonJS 是一种用于 Node.js 环境的模块化系统,而 ES6 Module 是 JavaScript 的官方模块化系统,用于现代浏览器和许多开发环境。尽管它们在语法和语义上存在差异,但两者都可以用于组织和管理 JavaScript 代码中的模块化。在选择使用哪种模块化系统时,请根据具体的应用场景和要求进行评估。

  

🔹讨论它们在模块加载机制上的不同

CommonJS 和 ES6 Module 在模块加载机制上有以下主要不同点:

  1. 导出和导入方式:
  • CommonJS 使用module.exports对象来导出模块中的内容,使用require()函数来导入其他模块中的内容。
  • ES6 Module 使用export关键字来导出模块中的内容,使用import关键字来导入其他模块中的内容。
  1. 模块的作用域:
  • CommonJS 采用全局模块作用域,所有模块中的变量和函数都是全局的。
  • ES6 Module 采用模块自身的作用域,每个模块中的变量和函数都是私有的,只能在该模块内部访问。
  1. 动态加载:
  • CommonJS 支持动态加载模块,通过require()函数可以在运行时动态地加载模块。
  • ES6 Module 也支持动态加载,但需要使用import()函数,并且需要在支持的环境中运行。
  1. 循环依赖:
  • CommonJS 不支持循环依赖,即一个模块不能依赖于它本身或其他模块中依赖它的模块。
  • ES6 Module 支持循环依赖,模块可以在导入时进行解析和处理。

总的来说,CommonJS 和 ES6 Module 在模块的导出和导入方式、作用域、动态加载以及循环依赖等方面存在差异。ES6 Module 是 JavaScript 的原生模块系统,提供了更现代化和简洁的模块化编程方式,而 CommonJS 则是一种早期的模块规范,仍然在一些旧项目中使用。

  

使用 CommonJS 和 ES6 Module 的注意事项🔖


分享一些在使用 CommonJS 和 ES6 Module 时需要注意的事项

  • 导出和导入的名称:在 CommonJS 中,导出的内容可以通过任意名称导出,而在 ES6 Module 中,导出的内容必须使用export关键字指定导出的名称。同样,在导入时也需要使用对应的导入名称。
  • 模块的默认导出:ES6 Module 支持默认导出,即可以使用export default导出一个默认值。在导入时可以省略导入的名称。
  • 导出的多个内容:在 CommonJS 中,可以通过多次调用module.exports来导出多个内容。而在 ES6 Module 中,每个模块只能有一个默认导出,其他内容需要使用具名导出。
  • 导入的默认值和具名值:在 ES6 Module 中,可以同时导入默认值和具名值。例如,可以使用import myModule, { export1, export2 } from 'myModule'
  • 动态导入:ES6 Module 支持使用import()函数进行动态导入,这在需要按需加载模块时非常有用。但需要注意,动态导入需要在支持的环境中运行。
  • 模块的路径:在 CommonJS 中,模块的路径是相对于模块文件的。而在 ES6 Module 中,模块的路径是相对于根目录或指定的模块目录。
  • 模块的加载顺序:在 CommonJS 中,模块的加载顺序是同步的,按照代码的顺序加载。而在 ES6 Module 中,模块的加载是异步的,可能会根据需要进行延迟加载。
  • 兼容性:由于 CommonJS 和 ES6 Module 在语法和模块加载机制上有所不同,因此在使用时需要注意兼容性问题。一些旧的库或工具可能不支持 ES6 Module 的语法,需要进行相应的转换或处理。