CommonJS 与 ES6 模块引入的区别

346 阅读3分钟

在现代 JavaScript 开发中,模块化是一个重要的概念。它使得代码更易于维护和复用。JavaScript 有两种主要的模块系统:CommonJS 和 ES6 模块(也称为 ES Modules 或 ESM)。本文将详细讲解这两种模块系统的区别。

CommonJS 模块

CommonJS 是 Node.js 中使用的模块系统。它的主要特点包括:

  1. 同步加载:模块在运行时同步加载,这意味着模块的加载是阻塞的。
  2. 单个导出对象:每个模块返回一个单独的导出对象。
  3. 动态引入:可以在代码的任何地方引入模块。

示例

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 的标准模块系统。它的主要特点包括:

  1. 静态加载:模块在编译时静态加载,这意味着模块的依赖关系在编译时就确定了。这种设计有几个好处:

    • 优化:编译器可以更好地优化代码,因为它可以提前知道所有的依赖关系。
    • 循环依赖:静态分析可以更好地处理循环依赖。
    • 安全性:导出的绑定是只读的,防止了意外的重新赋值,确保模块接口的稳定性。
  2. 多个导出:可以从一个模块中导出多个绑定。

  3. 顶层引入:模块的引入必须在文件的顶部进行,不能在代码块中引入。

    注意,这里有个例外,动态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'
                }
            }
        ]
    }
};