模块化标准是规范模块定义、导入、导出的规则,确保不同模块之间可正确交互。前端JavaScript的模块化标准经历了从"民间/社区标准"到"官方标准"的发展,目前主流为ES6 Modules,以下是所有主流标准的详细解析。
模块化解决了这些问题:
- 函数
- 解决文件分解(全局污染)和聚合(依赖混乱)的问题
一、民间/社区标准(非官方,早期主流)
1. CommonJS(运行时方案)
简介:Node.js默认采用的模块化方案,基于文件定义模块,每个文件就是一个独立模块,最初用于服务器端,后被前端借用(需通过Webpack等工具打包适配浏览器)。
- 在
commonjs中每一个 js 文件都是一个单独的模块,我们可以称之为 module; - 该模块中,包含 CommonJS 规范的核心变量: exports、module.exports、require;
- exports 和 module.exports 可以负责对模块中的内容进行导出;
- require 函数可以帮助我们导入其他模块(自定义模块、系统模块、第三方库模块)中的内容;
核心语法(导入/导出):
-
导出:通过module.exports或exports对象暴露模块成员;
-
导入:通过require()函数加载模块,同步加载。
代码示例:
javascript
// 导出模块(myModule.js)
module.exports = function() {
console.log('Hello from CommonJS');
};
// 导入模块(index.js)
const myModule = require('./myModule');
myModule();
特点:
-
同步加载:require()调用时,程序会阻塞,等待模块加载完成,适合服务器端(文件读取速度快,阻塞影响小);
-
运行时解析:依赖关系在代码执行时动态解析,无法在编译阶段优化;
-
单例模式:每次require()同一个模块,返回的是同一个实例,避免重复加载。
CommonJS 在编译阶段会对模块代码进行首尾包装,形成独立作用域,解决全局污染问题,包装函数如下:
javascript
function wrapper (script) {
return '(function (exports, require, module, __filename, __dirname) {' +
script +
'\n})'
}
- require 核心机制
require是 CommonJS 的核心导入方法,其加载流程、缓存机制、循环引用处理是核心考点。
(1)文件加载流程
require 接收标识符 作为参数,根据标识符类型按优先级查找模块,优先级:缓存 > 核心模块 > 路径文件模块 > 自定义模块
- 核心模块:Node.js 底层内置(如 fs、path),已编译为二进制,加载速度最快;
- 路径文件模块 :以
./、../、/开头,转换为真实路径后加载; - 自定义模块 :第三方库(如 crypto-js),从当前目录
node_modules开始向上递归查找,优先读取package.json的 main 属性,无则依次查找index.js/.json/.node。
(2)缓存与防重复加载
Node.js 存在Module全局对象,用于缓存已加载的模块,核心逻辑:
- 加载模块时先查找
Module._cache,有缓存则直接返回module.exports; - 无缓存则创建
module对象(含exports: {}、loaded: false),先加入缓存再执行模块代码; - 执行完成后标记
loaded: true,返回module.exports。效果:同一模块被多次 require 时,仅执行一次,后续直接读取缓存。
(3)循环引用处理
CommonJS 通过先缓存后执行的机制解决循环引用,核心逻辑:
- 模块 A 加载模块 B 时,先将 A 加入缓存,再执行 A 的代码;
- A 执行中加载 B,B 加入缓存后执行 B 的代码,B 中再加载 A 时,直接读取 A 的缓存(此时 A 未执行完毕,exports 可能为空 / 部分导出);
- B 执行完毕后,A 继续执行剩余代码,补全 exports。注意 :循环引用时,同步上下文可能获取不到模块未执行的导出内容,可通过异步 / 动态加载解决。
(4)动态加载特性
require 是普通函数,可在任意上下文(函数、条件语句)中动态加载模块,按需引入,示例:
javascript
// a.js
console.log('我是a文件')
exports.say = function() {
// 动态加载b.js
const getMes = require('./b')
console.log(getMes())
}
2. AMD(Asynchronous Module Definition,异步模块定义)
简介:专门为浏览器端设计的异步模块化标准,解决CommonJS同步加载在浏览器端的阻塞问题(浏览器加载文件需通过网络,同步加载会导致页面卡顿),常与RequireJS(模块加载器)配合使用。
核心语法(导入/导出):
-
导出:通过define()函数定义模块,可指定依赖;
-
导入:通过require()函数异步加载模块,加载完成后执行回调函数。
代码示例:
javascript
// 定义模块(myModule.js),依赖dependency模块
define(['dependency'], function(dependency) {
return function() {
console.log('Hello from AMD');
};
});
// 加载模块(index.js)
require(['myModule'], function(myModule) {
myModule();
});
特点:
-
异步加载:模块加载不阻塞主线程,适合浏览器环境;
-
提前声明依赖:define()中需提前指定依赖,加载器可并行加载多个依赖模块;
-
兼容性:需引入RequireJS等加载器,才能在浏览器中运行。
3. UMD(Universal Module Definition,通用模块定义)
简介:一种兼容多模块系统的格式,旨在让同一个模块能在CommonJS、AMD、浏览器全局变量(<script>标签引入)三种环境中无缝运行,解决模块跨环境复用问题。
核心逻辑:通过判断环境(是否支持AMD、CommonJS),自动适配对应的模块规范。
代码示例:
javascript
(function (root, factory) {
if (typeof define === 'function' && define.amd) {
// 适配AMD环境(如RequireJS)
define(['b'], factory);
} else if (typeof exports === 'object') {
// 适配CommonJS环境(如Node.js)
module.exports = factory(require('b'));
} else {
// 适配浏览器全局变量环境(<script>标签引入)
root.returnExports = factory(root.b);
}
}(this, function (b) {
// 模块核心逻辑
return {
greet: function() {
console.log('Hello from UMD');
}
};
}));
二、官方标准(ECMAScript规范,目前主流)
ES6 Module 是JavaScript 官方原生模块化规范 ,从 ES6 开始纳入标准,解决了 CommonJS 仅适用于服务端的问题,核心支持静态编译,是现代前端模块化的首选方案。
1. 核心优势
- 静态导入导出,支持Tree Shaking(摇树优化,删除未使用代码);
- 支持import () 动态导入,实现懒加载和代码分割;
- 原生支持浏览器和服务端(Node.js 14 + 稳定支持);
- 严格模式运行,避免全局污染。
2. 核心导入导出语法
ES Module 通过export 导出、import 导入,支持命名导出 / 默认导出 / 混合导出
| 导出方式 | 示例代码(a.js) | 导入方式 | 示例代码(main.js) |
|---|---|---|---|
| 命名导出 | export const name = 'xxx';export function say() {} | 命名导入 | import { name, say } from './a.js'; |
| 默认导出 | export default { name: 'xxx', author: 'xxx' } | 默认导入 | import mes from './a.js';(自定义名称) |
| 混合导出 | export const name = 'xxx';export default function say() {} | 混合导入 | import theSay, { name } from './a.js'; |
| 重命名导出 | export { name as bookName } from './a.js'; | 重命名导入 | import { name as bookName } from './a.js'; |
| 重定向导出 | export * from './a.js';(排除 default) | - | - |
| 仅执行模块 | - | 仅执行导入 | import './a.js';(仅运行,不导出) |
3. 核心特性
(1)静态语法特性
- import/export必须放在模块顶层,不能放在块级作用域、条件语句、函数中;
- 导入名不能是动态字符串(如
import ('default' + name) from './a.js'); - 编译阶段确定依赖关系,支持静态分析、Tree Shaking、类型检查。
(2)执行特性
ES Module 采用提前加载 + 深度优先遍历 ,执行顺序为子 -> 父 ,与 CommonJS(父 -> 子 -> 父)不同;同时具备缓存机制,同一模块被多次导入时,仅执行一次。
javascript
// main.js
console.log('main开始')
import from './a'
import from './b'
console.log('main结束')
// a.js
import from './b'
console.log('a加载')
// b.js
console.log('b加载')
// 执行顺序:b加载 → a加载 → main开始 → main结束
(3)导出绑定特性
ES Module 的导入变量是只读的、与原模块动态绑定的,核心规则:
- 导入变量默认为
const,直接赋值会报错 (如num = 2); - 变量是引用传递,原模块修改变量,导入方会实时同步;
- 可通过原模块的方法修改变量,导入方获取最新值。
javascript
// a.js
export let num = 1
export const add = () => { num++ }
// main.js
import { num, add } from './a.js'
console.log(num) // 1
add()
console.log(num) // 2
num = 3 // 报错:num is read-only
4. import () 动态导入
为解决 ES Module 静态语法的局限性,提供import () 动态导入特性,是现代前端懒加载的核心实现。
核心特点
- 函数形式,可在任意上下文中使用(条件、函数);
- 返回Promise 对象,then 回调中获取模块导出内容;
- 支持代码分割,按需加载,避免首次加载过大。
javascript
// 动态加载b.js
setTimeout(() => {
import('./b.js').then(res => {
console.log(res.name) // 命名导出
console.log(res.default) // 默认导出
})
}, 0)
实际应用
- Vue 路由懒加载 :
component: () => import('./Home.vue'); - React 懒加载 :
React.lazy(() => import('./LazyComponent')); - 按需加载业务模块:条件判断加载不同的功能模块。
5. Tree Shaking 实现
Tree Shaking 是 ES Module 的核心优化,依赖静态语法 特性,在编译阶段分析模块导出与导入的引用关系,删除未被使用的代码,减少打包体积。
javascript
// a.js:导出3个成员,仅add被使用
export let num = 1
export const add = () => { num++ }
export const del = () => { num-- }
// main.js:仅导入add
import { add } from './a.js'
add()
// 打包结果:仅保留num和add,del被删除
三、CommonJS 与 ES Module 核心对比
| 对比维度 | CommonJS | ES Module |
|---|---|---|
| 标准类型 | 社区规范,非官方 | ES6 官方原生规范 |
| 运行环境 | 主要适用于 Node.js(服务端),前端需打包兼容 | 浏览器 + Node.js(14+)原生支持 |
| 加载方式 | 同步加载,运行时解析 | 静态编译(顶层)+ 异步加载(import ()),编译时解析 |
| 执行顺序 | 深度优先,父 -> 子 -> 父 | 深度优先,子 -> 父 |
| 导出方式 | 单个值导出,最终导出 module.exports | 支持多成员命名导出、默认导出 |
| 变量绑定 | 值拷贝,基本类型导出值,引用类型导出地址 | 动态绑定,引用传递,原模块修改实时同步 |
| 导入变量特性 | 可修改导入的引用类型属性 | 导入变量只读,不可直接赋值 |
| 核心优化 | 无原生优化,依赖打包工具 | 支持 Tree Shaking、代码分割(import ()) |
| 循环引用处理 | 先缓存后执行,同步上下文可能获取不到完整导出 | 静态解析,提前确定依赖,绑定关系实时同步 |
| 缓存机制 | 基于 Module._cache 缓存 module 对象 | 模块缓存,同一模块仅执行一次 |