CommonJS 和 ES6 模块

158 阅读5分钟

1. CommonJS 模块(CJS)

1.1 什么是 CommonJS?

CommonJS 是一种用于在 Node.js 中实现模块化的规范,它定义了一套模块化的 API,允许开发者在 JavaScript 中将代码分割成多个独立的模块。CommonJS 最初是为了让 JavaScript 在服务器端的 Node.js 环境中也能够像其他语言一样支持模块化而设计的。

1.2 CommonJS 的核心特性

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

  • 导入模块:使用 require() 函数加载模块。

例如:

// math.js
function add(num1, num2) {
  return num1 + num2;
}
function subtract(num1, num2) {
  return num1 - num2;
}
module.exports = {
  add: add,
  subtract: subtract
};
// main.js
const math = require('./math');
console.log(math.add(5, 3)); 

1.3 CommonJS 的特点

  • 同步加载:在 Node.js 中,require() 是同步的,这意味着当 require() 加载一个模块时,当前线程会等待该模块加载完成后再继续执行。这在服务器端的文件系统中非常有效,因为文件加载速度较快。
  • 适用于服务器端:由于 CommonJS 是同步加载的,它在 Node.js 服务器端的文件加载时非常合适。但是,这在浏览器环境中可能会带来性能问题,特别是当网络延迟变得明显时。
  • 模块缓存:模块在第一次被 require() 加载时会被缓存,后续的加载操作将直接使用缓存中的模块,这提高了性能。

1.4 CommonJS 的缺点

  • 不适合浏览器:由于 require() 是同步操作,且依赖于文件系统(比如 Node.js 中的 fs 模块),它并不适合在浏览器环境中使用。
  • 模块化与前端构建工具不兼容:虽然可以通过一些工具(如 Browserify 或 Webpack)将 CommonJS 模块打包到浏览器中,但这种方法并不原生支持浏览器。

2. ES6 模块(ESM)

2.1 什么是 ES6 模块?

ES6(ECMAScript 2015)引入了模块化的原生支持,这一规范允许开发者使用 importexport 语法来进行模块化开发。ES6 模块化不仅可以在浏览器环境中使用,还能在现代的 JavaScript 引擎中支持,比如 Node.js(从 v12 版本开始)和浏览器。

2.2 ES6 模块的核心特性

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

  • 导入模块:使用 import 导入其他模块中的功能。

例如:

// person.js
export const name = 'Alice';
export function sayHello() {
  console.log('Hello!');
}
export default class Person {
  constructor(age) {
    this.age = age;
  }
}
// main.js
import Person, { name, sayHello } from './person';
const person = new Person(25);
console.log(name);
sayHello();

2.3 ES6 模块的特点

  • 静态分析:ES6 模块是静态的,意味着在编译时就能确定模块的结构和依赖关系。这样,现代构建工具(如 Webpack、Rollup、Parcel)可以对模块进行优化,进行树摇优化(Tree Shaking),只打包用到的部分,减少最终的包大小。
  • 异步加载:虽然 ES6 模块本身是同步的,但它可以和动态 import() 结合使用,实现按需加载,支持异步加载模块。
  • 浏览器支持:现代浏览器原生支持 ES6 模块,不需要任何额外的工具(如 Browserify)来进行转换。
  • 作用域:ES6 模块的导出变量是 只读 的,这意味着模块外部无法修改它们。并且,ES6 模块使用 严格模式,不会意外的创建全局变量。

2.4 ES6 模块的缺点

  • 暂时不兼容老版本的 Node.js:虽然 ES6 模块在现代浏览器中得到广泛支持,但在 Node.js 之前的版本并不支持。直到 Node.js 12(LTS 版本)才开始正式支持 ES6 模块,不过在一些老旧的项目中,可能需要转化为 CommonJS 格式。
  • 必须使用 .mjs 后缀或配置:为了区别 CommonJS 和 ES6 模块,Node.js 在支持 ES6 模块时,要求文件使用 .mjs 后缀,或者通过 package.json 中的 "type": "module" 配置来声明。

3. 对比

1. 语法形式

  • CommonJS:使用require()函数来引入模块,通过module.exports对象导出模块的内容。
  • ES6 模块:使用import关键字引入模块,使用export关键字导出模块内容。有多种导出形式,包括默认导出和命名导出。

2. 加载机制

  • CommonJS:是运行时加载模块。当执行到require()语句时,才会去加载对应的模块,并且这个加载过程是同步的。这意味着如果一个模块加载时间较长,会阻塞后续代码的执行。在服务器端(如 Node.js)环境中,由于模块通常是从本地文件系统加载,这种同步加载对性能的影响相对较小。
  • ES6 模块:是在编译阶段就确定模块的依赖关系,属于静态加载。浏览器或其他支持 ES6 模块的环境在加载模块时,可以利用这种静态信息进行优化,例如进行 tree - shaking(去除未使用的代码),提高加载效率和代码的执行效率。虽然 ES6 模块的加载本质上是同步的,但它们本身支持 异步加载,可以使用 import() 动态导入模块,从而实现按需加载(Lazy Loading),进一步提高性能。

3. 模块的引用方式

  • CommonJS:每次使用require()引入一个模块时,得到的是该模块的一个副本。如果模块内部的值发生变化,不会影响其他已经引入该模块的地方。
  • ES6 模块import语句引入的是模块内容的一个动态引用(对于可写绑定的情况)。如果模块内导出的值发生变化,在其他导入该模块的地方能反映出来。不过,对于const声明的导出值这种不可变绑定则不会有这种变化。

image.png