在现代 JavaScript 开发中,模块化是一个重要的概念。它使得代码更易于维护和复用。JavaScript 有两种主要的模块系统:CommonJS 和 ES6 模块(也称为 ES Modules 或 ESM)。本文将详细讲解这两种模块系统的区别。
CommonJS 模块
CommonJS 是 Node.js 中使用的模块系统。它的主要特点包括:
- 同步加载:模块在运行时同步加载,这意味着模块的加载是阻塞的。
- 单个导出对象:每个模块返回一个单独的导出对象。
- 动态引入:可以在代码的任何地方引入模块。
示例
math.js:
function add(a, b) {
return a + b;
}
function subtract(a, b) {
return a - b;
}
module.exports = {
add,
subtract
};
app.js:
const math = require('./math');
console.log(math.add(2, 3)); // 5
console.log(math.subtract(5, 2)); // 3
导出对象的修改
CommonJS 导出的对象是一个引用,因此在其他模块中修改导出的对象会影响到所有引用该模块的地方。
config.js:
javascript
Copy code
module.exports = {
setting: 'default'
};
app.js:
javascript
Copy code
const config = require('./config');
console.log(config.setting); // 'default'
config.setting = 'custom';
console.log(config.setting); // 'custom'
anotherApp.js:
javascript
Copy code
const config = require('./config');
console.log(config.setting); // 'custom'
在 anotherApp.js 中,config.setting 的值已经被 app.js 修改为 'custom',这表明 CommonJS 导出的对象是共享的。
ES6 模块
ES6 模块是 JavaScript 的标准模块系统。它的主要特点包括:
-
静态加载:模块在编译时静态加载,这意味着模块的依赖关系在编译时就确定了。这种设计有几个好处:
- 优化:编译器可以更好地优化代码,因为它可以提前知道所有的依赖关系。
- 循环依赖:静态分析可以更好地处理循环依赖。
- 安全性:导出的绑定是只读的,防止了意外的重新赋值,确保模块接口的稳定性。
-
多个导出:可以从一个模块中导出多个绑定。
-
顶层引入:模块的引入必须在文件的顶部进行,不能在代码块中引入。
注意,这里有个例外,动态import()是可以在函数或者条件语句中使用的,这也是我们通常
code splitting所依赖的特性。
示例
math.js:
export function add(a, b) {
return a + b;
}
export function subtract(a, b) {
return a - b;
}
app.js:
import { add, subtract } from './math';
console.log(add(2, 3)); // 5
console.log(subtract(5, 2)); // 3
导出对象的修改
ES6 模块导出的绑定是只读的,不能直接修改导出的绑定,但可以修改导出的对象的属性。
config.js:
javascript
Copy code
export const config = {
setting: 'default'
};
app.js:
javascript
Copy code
import { config } from './config';
console.log(config.setting); // 'default'
config.setting = 'custom';
console.log(config.setting); // 'custom'
anotherApp.js:
javascript
Copy code
import { config } from './config';
console.log(config.setting); // 'custom'
在 anotherApp.js 中,config.setting 的值已经被 app.js 修改为 'custom',这表明 ES6 模块导出的对象也是共享的。
详细对比
1. 导出和引入方式
- CommonJS 使用
module.exports导出,使用require引入。 - ES6 模块 使用
export导出,使用import引入。
2. 加载方式
- CommonJS 是同步加载,适用于服务器端。
- ES6 模块 是静态加载,适用于浏览器和服务器端。
3. 导出内容
- CommonJS 导出的是一个对象,所有导出的内容都作为对象的属性。
- ES6 模块 可以导出多个绑定,导出的是绑定的引用。
4. 动态引入
- CommonJS 可以在代码的任何地方动态引入模块。
- ES6 模块 需要在顶层作用域静态引入,但可以使用
import()进行动态引入。
5. 兼容性
- CommonJS 主要用于 Node.js 环境。
- ES6 模块 是 JavaScript 的标准,现代浏览器和 Node.js 都支持。
兼容性处理
在实际开发中,有时需要在同一个项目中使用两种模块系统。可以使用工具如 Babel 和 Webpack 来处理兼容性问题。
使用 Babel 转换 ES6 模块
Babel 可以将 ES6 模块转换为 CommonJS 模块:
npm install --save-dev @babel/core @babel/preset-env
babel.config.js:
module.exports = {
presets: ['@babel/preset-env']
};
使用 Webpack 打包
Webpack 可以处理两种模块系统,并将它们打包在一起:
npm install --save-dev webpack webpack-cli babel-loader @babel/core @babel/preset-env
webpack.config.js:
const path = require('path');
module.exports = {
entry: './src/index.js',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist')
},
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader'
}
}
]
}
};