1. CommonJS 模块(CJS)
1.1 什么是 CommonJS?
CommonJS 是一种用于在 Node.js 中实现模块化的规范,它定义了一套模块化的 API,允许开发者在 JavaScript 中将代码分割成多个独立的模块。CommonJS 最初是为了让 JavaScript 在服务器端的 Node.js 环境中也能够像其他语言一样支持模块化而设计的。
1.2 CommonJS 的核心特性
-
导出模块:使用
module.exports或exports对象来导出模块中的内容。 -
导入模块:使用
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)引入了模块化的原生支持,这一规范允许开发者使用 import 和 export 语法来进行模块化开发。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声明的导出值这种不可变绑定则不会有这种变化。