JavaScript 模块化规范(CommonJS、ES Module)
模块化将代码分割为独立单元,CommonJS 是 Node.js 传统规范,ES Module 是 ES6 标准规范,两者各有特点。
CommonJS 规范
JavaScript
// 导出模块
// 方式一:module.exports
module.exports = {
name: 'utils',
add: (a, b) => a + b,
subtract: (a, b) => a - b
};
// 方式二:exports(是module.exports 的引用)
exports.multiply = (a, b) => a * b;
exports.divide = (a, b) => a / b;
// 注意:不能直接替换 exports
exports = { name: 'error' }; // 错误!不会影响 module.exports
// 导入模块
const utils = require('./utils');
console.log(utils.add(1, 2)); // 3
// 解构导入
const { add, subtract } = require('./utils');
// 导入核心模块
const fs = require('fs');
const path = require('path');
// 导入 npm 包
const express = require('express');
const lodash = require('lodash');
CommonJS 特性
JavaScript
// 1. 同步加载(适合服务器端)
const utils = require('./utils'); // 同步读取文件
console.log('加载完成');
// 2. 动态加载(运行时决定)
const moduleName = process.env.MODULE;
const module = require(`./modules/${moduleName}`); // 动态路径
// 3. 输出值拷贝
// counter.js
let count = 0;
module.exports = {
getCount: () => count,
increment: () => count++
};
// main.js
const counter = require('./counter');
console.log(counter.getCount()); // 0
counter.increment();
console.log(counter.getCount()); // 1
// 原模块的 count 变化,但导出的 getCount 函数能访问到
// 4. 单例模式(缓存)
// 多次 require 同一模块返回同一对象
const a = require('./utils');
const b = require('./utils');
console.log(a === b); // true(模块缓存)
ES Module 规范
JavaScript
// 导出模块
// 方式一:命名导出
export const name = 'utils';
export function add(a, b) {
return a + b;
}
export function subtract(a, b) {
return a - b;
}
// 方式二:集中导出
const multiply = (a, b) => a * b;
const divide = (a, b) => a / b;
export { multiply, divide };
// 方式三:默认导出
export default {
name: 'utils',
add: (a, b) => a + b
};
// 方式四:混合导出
export default class Calculator { }
export const helper = () => {};
// 导入模块
// 命名导入
import { add, subtract } from './utils.js';
console.log(add(1, 2)); // 3
// 默认导入
import Calculator from './utils.js';
// 混合导入
import Calculator, { helper } from './utils.js';
// 全部导入
import * as utils from './utils.js';
console.log(utils.add(1, 2));
// 重命名导入
import { add as sum } from './utils.js';
console.log(sum(1, 2));
ES Module 特性
JavaScript
// 1. 异步加载(适合浏览器)
import { add } from './utils.js'; // 异步加载
// 代码继续执行,模块加载完成后执行导入
// 2. 静态加载(编译时确定)
import { add } from './utils.js'; // import 必须在顶层
// 不能动态导入:import(`./${module}.js`) // SyntaxError
// 动态导入使用 import()
const moduleName = 'utils';
import(`./modules/${moduleName}.js`).then(module => {
module.add(1, 2);
});
// 3. 输出值引用(绑定)
// counter.js
export let count = 0;
export function increment() {
count++;
}
// main.js
import { count, increment } from './counter.js';
console.log(count); // 0
increment();
console.log(count); // 1(实时更新!)
// ES Module 导出的是值的引用,不是拷贝
// 4. 严格模式
// ES Module 自动启用严格模式,无需 'use strict'
export const obj = {}; // this === undefined
CommonJS vs ES Module 对比
| 特性 | CommonJS | ES Module |
|---|---|---|
| 加载时机 | 运行时加载 | 编译时静态分析 |
| 加载方式 | 同步 | 异步 |
| 输出方式 | 值拷贝 | 值引用(实时绑定) |
| this | 指向当前模块 | undefined |
| 动态导入 | require(path) | import(path)返回Promise |
| 严格模式 | 需手动开启 | 自动开启 |
| 文件扩展名 | 可省略 | 必须完整(浏览器) |
| 循环依赖 | 只输出已执行部分 | 动态引用 |
循环依赖处理
JavaScript
// CommonJS 循环依赖
// a.js
const b = require('./b');
console.log('a: b.value =', b.value);
module.exports.value = 'a';
// b.js
const a = require('./a'); // a 模块部分执行,exports 为 {}
console.log('b: a.value =', a.value); // undefined
module.exports.value = 'b';
// 执行顺序:
// a 开始执行 → require b → b 开始执行 → require a → a 已加载,返回部分 exports
// b 输出 undefined → b 完成 → a 继续 → a 输出 'b'
// ES Module 循环依赖
// a.mjs
import { value } from './b.mjs';
console.log('a: b.value =', value); // 'b'
export const value = 'a';
// b.mjs
import { value } from './a.mjs';
console.log('b: a.value =', value); // undefined(引用,未执行到)
export const value = 'b';
// ES Module 使用动态引用,访问时才取值
// 如 b.mjs 延迟访问 a.value,会得到 'a'
Node.js 使用 ES Module
JavaScript
// 方式一:.mjs 文件扩展名
// utils.mjs
export const add = (a, b) => a + b;
// 方式二:package.json type: "module"
{
"type": "module"
}
// 所有 .js 文件作为 ES Module
// 方式三:--experimental-modules(旧版本)
node --experimental-modules index.mjs
// Node.js 内置模块 ES Module 导入
import fs from 'fs';
import { readFileSync } from 'fs';
import fsPromises from 'fs/promises'; // Promise 版本
// CommonJS 与 ES Module 混用限制
// ES Module 中不能直接 require
import { createRequire } from 'module';
const require = createRequire(import.meta.url);
const cjsModule = require('./cjs-module.cjs');
浏览器使用 ES Module
HTML
<!-- script type="module" -->
<script type="module">
import { add } from './utils.js';
console.log(add(1, 2));
</script>
<!-- 外部模块 -->
<script type="module" src="./app.js"></script>
<!-- 动态导入 -->
<script type="module">
const module = await import('./utils.js');
console.log(module.add(1, 2));
</script>
<!-- 注意:浏览器中路径必须完整 -->
import { add } from './utils.js'; // 正确
import { add } from './utils'; // 错误(缺少.js)
<!-- nomodule 兜底 -->
<script type="module" src="./app.js"></script>
<script nomodule src="./app-legacy.js"></script>
import() 动态导入
JavaScript
// 动态导入:返回 Promise
async function loadModule(name) {
const module = await import(`./modules/${name}.js`);
return module;
}
// 按需加载
button.addEventListener('click', async () => {
const { showDialog } = await import('./dialog.js');
showDialog();
});
// 懒加载路由
const routes = {
home: () => import('./pages/Home.js'),
about: () => import('./pages/About.js')
};
async function loadPage(page) {
const module = await routes[page]();
return module.default;
}
// 条件导入
if (featureEnabled) {
const feature = await import('./feature.js');
feature.init();
}
注意事项
- ES Module 文件扩展名:Node.js 用
.mjs或type: "module",浏览器必须完整路径- ES Module 自动严格模式,
this是undefined- CommonJS 模块有缓存,多次
require返回同一对象- ES Module 导出值引用,导入值会实时更新
import()动态导入返回 Promise,可实现懒加载
JavaScript
// CommonJS 模块缓存
// utils.js
console.log('utils loaded');
module.exports = { name: 'utils' };
// main.js
require('./utils'); // 打印 'utils loaded'
require('./utils'); // 不打印(缓存)
// 清除缓存(开发调试用)
delete require.cache[require.resolve('./utils')];
require('./utils'); // 再次打印
// ES Module 无缓存机制,但浏览器会缓存 HTTP 请求
// Node.js ES Module 有内部缓存
要点总结
- CommonJS:Node.js 模块规范,同步加载,值拷贝,运行时解析
- ES Module:JavaScript 标准规范,异步加载,值引用,静态分析
- CommonJS 导出:
module.exports、exports,导入:require() - ES Module 导出:
export、export default,导入:import - CommonJS 可动态加载,ES Module 静态加载,
import()可动态 - ES Module 导出值引用,导入值实时更新
- 循环依赖:CommonJS 输出已执行部分,ES Module 动态引用
- Node.js:
.mjs或type: "module"启用 ES Module - 浏览器:
<script type="module">,路径必须完整
📝 发现内容有误?点击此处直接编辑