JavaScript 模块化完全指南:CommonJS、ES Module 与动态导入

小飞兽 JavaScript 515 次阅读 2026-07-01

Introduction

JavaScript 模块化经历了从无到有、从混乱到统一的过程。早期靠全局变量和 IIFE 模拟模块化,后来出现了 CommonJS(Node.js)、AMD、UMD 等规范。ES6 标准引入了官方的 ES Module(import/export)语法,成为浏览器和 Node.js 的通用标准。本文对比主流模块化方案并讲解最佳实践。

ES Module 基础语法

/* 命名导出 */
export const PI = 3.14159;
export function add(a, b) { return a + b; }

/* 命名导入 */
import { PI, add } from './math.js';
console.log(add(PI, 2));

/* 默认导出 */
export default function greet(name) {
return 'Hello, ' + name;
}

/* 默认导入 */
import greet from './greet.js';
greet('小马');

/* 命名 + 默认混合 */
import defaultFn, { named1, named2 } from './module.js';

/* 重命名导入 */
import { oldName as newName } from './module.js';

CommonJS(Node.js 环境)

/* 导出(Node.js 端)*/
module.exports = { add, subtract };
// 或
exports.add = add;
exports.subtract = subtract;

/* 导入 */
const { add, subtract } = require('./math');
const math = require('./math');

/* ES Module 与 CommonJS 的区别 */
/* ES Module: 静态 import/export,编译时解析,支持异步加载 */
/* CommonJS: 动态 require,运行时解析,同步加载 */

动态导入

/* 动态 import() 返回 Promise */
button.addEventListener('click', async function() {
    const { greet } = await import('./greet.js');
    console.log(greet('小马'));
});

/* 按条件加载模块 */
if (condition) {
import('./heavy.js').then(module => {
module.init();
});
}

/* 路由懒加载(React/Vue)*/
const routes = [
{ path: '/home', component: () => import('./Home.vue') },
{ path: '/about', component: () => import('./About.vue') }
];

模块作用域与循环引用

/* 每个 ES Module 有独立的作用域 */
/* a.js */
import { b } from './b.js';
export const a = 'a';
console.log(b); // 'b'

/* b.js */
import { a } from './a.js';
export const b = 'b';
console.log(a); // 'a'

/* 循环引用处理:先导出未完成的模块(hoisting)*/
/* 最佳实践:避免循环依赖,必要时重构代码结构 */

浏览器中 ES Module 的使用

<!-- type="module" 让浏览器以 ES Module 方式加载脚本 -->
<script type="module" src="./app.js"></script>

/* 内联 module 脚本 */
<script type="module">
import { greet } from './greet.js';
document.body.innerHTML = '<p>' + greet('世界') + '</p>';
</script>

/* ES Module 总是 defer(延迟执行)*/
/* 脚本按出现顺序执行,document parsing 不会阻塞 */

常见问题

    • import vs require:import 是静态声明(在编译时解析),require 是动态调用(在运行时执行)。ES Module 支持 tree shaking,require 不支持。
    • 循环引用:CommonJS 中 module.exports 在运行时构建,循环引用可能导致获取到不完整的对象。ES Module 有 hoisting 机制,相对更健壮。
    • CORS 限制:ES Module 从 CDN 加载需要正确的 CORS 头,本地文件(file://)在浏览器中无法加载 ES Module。
    • Node.js 中使用 ES Module:需要将 package.json 中 type 设为 module,或文件后缀改为 .mjs。

    延伸阅读

  • Node.js 模块文档:Node.js Modules