CommonJS 与 ES6 模块引入的区别详解

随着 JavaScript 的发展,模块化编程已经成为现代前端开发的基础。目前主流的模块系统有两种:CommonJS 和 ES6 模块。本文将详细对比这两种模块系统的语法、特性和使用场景。

一、CommonJS 模块系统

CommonJS 最初是为了让 JavaScript 能在服务端(如 Node.js)运行而设计的模块规范。

1. 基本语法

导出模块

javascript 复制代码
// 方式一:直接导出单个值
// moduleA.js
const name = 'John';
module.exports = name;
​
// 方式二:导出一个对象
// person.js
const person = { 
  name: 'John', 
  age: 30,
  greet() {
    console.log(`Hello, I'm ${this.name}`);
  }
};
module.exports = person;
​
// 方式三:使用 exports 快捷方式
// utils.js
exports.add = (a, b) => a + b;
exports.subtract = (a, b) => a - b;
// 等价于:
// module.exports = { add, subtract }

引入模块

javascript 复制代码
// main.js
// 引入单个值
const name = require('./moduleA');
console.log(name); // 'John'
​
// 引入对象
const person = require('./person');
console.log(person.name); // 'John'
console.log(person.age);  // 30
person.greet(); // "Hello, I'm John"
​
// 引入工具函数
const utils = require('./utils');
console.log(utils.add(5, 3)); // 8

2. 核心特性

动态引入

CommonJS 允许在代码运行时动态加载模块:

javascript 复制代码
// 可以根据条件动态引入
if (process.env.NODE_ENV === 'development') {
  const debugModule = require('./debug');
  debugModule.enable();
}
​
// 可以在函数内部引入
function loadModule(moduleName) {
  return require(`./modules/${moduleName}`);
}
​
// 可以在循环中引入
const modules = ['moduleA', 'moduleB', 'moduleC'];
modules.forEach(name => {
  const module = require(`./${name}`);
  module.init();
});

同步加载

CommonJS 的模块加载是同步的:

javascript 复制代码
// 同步加载,代码会等待模块加载完成
const fs = require('fs');        // 核心模块
const express = require('express'); // 第三方模块
const myModule = require('./my-module'); // 本地模块
​
console.log('模块加载完成,继续执行');

值的拷贝

CommonJS 导出的是值的拷贝:

javascript 复制代码
// counter.js
let count = 0;
module.exports = {
  count,
  increment() {
    count += 1;
  },
  getCount() {
    return count;
  }
};
​
// main.js
const counter = require('./counter');
console.log(counter.count); // 0
counter.increment();
console.log(counter.count); // 0 (仍然是0,因为 count 是原始值的拷贝)
console.log(counter.getCount()); // 1 (需要通过方法获取最新值)

二、ES6 模块系统

ES6 模块是 ECMAScript 2015 中引入的官方模块规范,现已被现代浏览器和 Node.js 支持。

1. 基本语法

导出模块

javascript 复制代码
// 方式一:命名导出(逐个导出)
// person.js
export const name = 'John';
export const age = 30;
export function greet() {
  console.log(`Hello, I'm ${this.name}`);
}
​
// 方式二:批量导出
// utils.js
const add = (a, b) => a + b;
const subtract = (a, b) => a - b;
export { add, subtract };
​
// 方式三:默认导出
// math.js
export default class Math {
  static pi = 3.14159;
  static square(x) {
    return x * x;
  }
}
​
// 方式四:混合导出
// shapes.js
export const PI = 3.14159;
export default class Circle {
  constructor(radius) {
    this.radius = radius;
  }
  area() {
    return PI * this.radius ** 2;
  }
}

引入模块

javascript 复制代码
// 引入命名导出
import { name, age, greet } from './person.js';
console.log(name, age);
greet();
​
// 引入并重命名
import { add as addNumbers, subtract } from './utils.js';
​
// 引入默认导出
import Math from './math.js';
console.log(Math.square(4));
​
// 同时引入默认和命名导出
import Circle, { PI } from './shapes.js';
​
// 引入所有导出(命名空间导入)
import * as utils from './utils.js';
console.log(utils.add(5, 3));
console.log(utils.subtract(5, 3));
​
// 只加载模块但不引入任何内容
import './styles.css';

2. 核心特性

静态引入

ES6 模块的引入必须位于顶层,不能动态引入(至少在基础语法上):

javascript 复制代码
// ✅ 正确:顶层引入
import { readFile } from 'fs';
​
// ❌ 错误:不能在条件语句中引入
if (condition) {
  import { readFile } from 'fs'; // 语法错误
}
​
// ❌ 错误:不能在函数中引入
function loadModule() {
  import { readFile } from 'fs'; // 语法错误
}

异步加载

但在实际使用中,可以通过动态 import() 实现异步加载:

javascript 复制代码
// ✅ 动态引入(返回 Promise)
if (condition) {
  import('./heavy-module.js')
    .then(module => {
      module.doSomething();
    })
    .catch(err => {
      console.error('模块加载失败', err);
    });
}
​
// 使用 async/await
async function loadAdminModule() {
  try {
    const adminModule = await import('./admin.js');
    adminModule.init();
  } catch (error) {
    console.error('加载失败', error);
  }
}
​
// 按需加载路由组件(Vue/React 常见用法)
const UserProfile = () => import('./views/UserProfile.vue');

值的引用

ES6 模块导出的是值的引用,导出和导入的变量指向同一块内存:

javascript 复制代码
// counter.js
export let count = 0;
export function increment() {
  count += 1;
}
​
// main.js
import { count, increment } from './counter.js';
console.log(count); // 0
increment();
console.log(count); // 1 (直接更新了)

三、核心区别对比

四、实际应用场景

1. CommonJS 适用场景

javascript 复制代码
// Node.js 服务端应用
const express = require('express');
const mongoose = require('mongoose');
const config = require('./config');
​
// 条件加载不同环境的配置
const env = process.env.NODE_ENV || 'development';
const dbConfig = require(`./config/${env}.js`);
​
// 动态加载插件
function loadPlugin(pluginName) {
  try {
    return require(`./plugins/${pluginName}`);
  } catch (err) {
    console.error(`插件 ${pluginName} 加载失败`);
    return null;
  }
}

2. ES6 模块适用场景

typescript 复制代码
// 现代前端应用(React/Vue 项目)
import React, { useState, useEffect } from 'react';
import { BrowserRouter, Route, Switch } from 'react-router-dom';
​
// 按需加载(代码分割)
const LazyComponent = React.lazy(() => import('./LazyComponent'));
​
// 明确导入需要的内容,便于 Tree Shaking
import { debounce, throttle } from 'lodash-es';
​
// 类型导入(TypeScript)
import type { User, Product } from './types';

五、混合使用注意事项

在 Node.js 环境中,可以混合使用两种模块系统,但需要注意:

javascript 复制代码
// ES6 模块中引入 CommonJS 模块
import package from 'commonjs-package'; // 默认导入
import { something } from 'commonjs-package'; // 命名导入(有限支持)
​
// CommonJS 中引入 ES6 模块(使用动态 import)
async function loadESModule() {
  const esModule = await import('./es-module.mjs');
  console.log(esModule.default);
}

package.json 配置

json 复制代码
{
  "name": "my-project",
  "version": "1.0.0",
  "type": "module", // 设置后,.js 文件默认使用 ES6 模块
  
  "exports": {
    ".": {
      "import": "./dist/index.mjs", // ES6 模块入口
      "require": "./dist/index.cjs"  // CommonJS 模块入口
    }
  }
}

总结

  1. CommonJS 适合 Node.js 服务端开发,特别是需要动态加载的场景

  2. ES6 模块 是现代前端开发的标准,支持静态分析和 Tree Shaking

  3. 动态 import() 填补了 ES6 模块的动态加载能力

  4. 实际开发 中,建议新项目优先使用 ES6 模块,可以获得更好的工具支持和性能优化

选择哪种模块系统,应根据项目运行环境、团队习惯和具体需求来决定。

相关推荐
橙子家25 分钟前
浏览器缓存之【结构化数据库与缓存】: IndexedDB、Cache storage 和 Storage buckets
前端
user205855615181330 分钟前
X6 中边悬浮置顶,规避 `mouseleave` 事件丢失问题
前端
李明卫杭州31 分钟前
CSS aspect-ratio 属性完全指南
前端
Pedantic2 小时前
SwiftUI 手势层级(Gesture Hierarchy)详解
前端
飘尘3 小时前
前端转型全栈(Java后端)的快速上手指引
前端·后端·全栈
一颗烂土豆3 小时前
Meshopt 压缩深度解析,为什么它比 Draco 更快
前端·javascript·webgl
浏览器工程师4 小时前
AI Agent 接浏览器任务,先别让它一路点到底
前端·后端
雨季mo浅忆4 小时前
VSCode自动格式化三要素
前端
爱勇宝5 小时前
深扒 Anthropic 1680 位工程师简历:应届生几乎没机会,AI 公司最缺的不是博士
前端·后端·程序员