大家好,我是鱼樱!!!
关注公众号【鱼樱AI实验室】
持续每天分享更多前端和AI辅助前端编码新知识~~喜欢的就一起学反正开源至上,无所谓被诋毁被喷被质疑文章没有价值~~~坚持自己观点
一个城市淘汰的自由职业-农村前端程序员(虽然不靠代码挣钱,写文章就是为爱发电),兼职远程上班目前!!!热心坚持分享~~~
目录
背景与演进
JavaScript模块化发展历程:
- 原始阶段(2009年前):全局变量污染、依赖管理混乱
- 社区方案阶段(2009-2015):CommonJS/AMD/CMD等方案涌现
- 标准统一阶段(2015+):ES Module成为语言标准
主流模块化标准
IIFE(立即调用函数表达式)已淘汰
javascript
// 定义模块
var module = (function() {
var privateVar = 'secret';
return {
publicMethod: function() {
return privateVar;
}
};
})();
// 使用模块
module.publicMethod();
特点:
- 通过闭包实现作用域隔离
- 无依赖管理能力
- 典型方案:早期jQuery插件
CommonJS
javascript
// math.js
const add = (a, b) => a + b;
module.exports = { add };
// app.js
const { add } = require('./math');
console.log(add(2, 3));
核心特征:
require()
同步加载module.exports
导出- 主要场景:Node.js环境
- 现代应用:仍广泛用于Node.js,通过打包工具用于浏览器
AMD(Asynchronous Module Definition)逐渐淘汰
javascript
// 定义模块
define(['dep1', 'dep2'], function(dep1, dep2) {
return {
doSomething: function() {
return dep1.action() + dep2.action();
}
};
});
// 加载模块
require(['module'], function(module) {
module.doSomething();
});
核心特征:
- 浏览器优先的异步加载
- 典型实现:RequireJS
- 现状:ES Module普及后使用率大幅下降
CMD(Common Module Definition)已淘汰
javascript
define(function(require, exports, module) {
var dep1 = require('./dep1');
exports.action = function() {
return dep1.doSomething();
};
});
核心差异:
- 延迟执行(vs AMD提前执行)
- 典型实现:Sea.js
- 现状:2016年后基本退出市场
UMD(Universal Module Definition)
javascript
(function(root, factory) {
if (typeof define === 'function' && define.amd) {
// AMD
define(['dep'], factory);
} else if (typeof module === 'object' && module.exports) {
// CommonJS
module.exports = factory(require('dep'));
} else {
// 全局变量
root.myModule = factory(root.dep);
}
})(this, function(dep) {
// 模块逻辑
return { /* ... */ };
});
核心价值:
- 兼容AMD/CommonJS/全局变量
- 现代应用:库开发仍需考虑多环境兼容时使用
ES Module(ECMAScript Modules)
javascript
// 导出模块
export const name = 'module';
export default function() { /* ... */ };
// 导入模块
import myModule, { name } from './module.js';
核心优势:
- 静态解析(编译时加载)
- 浏览器原生支持
- 支持Tree-shaking
- 循环依赖处理更优
现代扩展:
javascript
// 动态导入
const module = await import('./module.js');
// 聚合导出
export * from './base.js';
对比与现状
标准 | 加载方式 | 环境 | 静态分析 | 现状 |
---|---|---|---|---|
IIFE | 同步 | 浏览器 | ❌ | 淘汰 |
CommonJS | 同步 | Node | ❌ | Node主流 |
AMD | 异步 | 浏览器 | ❌ | 逐渐淘汰 |
CMD | 延迟 | 浏览器 | ❌ | 淘汰 |
UMD | 兼容 | 通用 | ❌ | 存量使用 |
ES Module | 静态 | 全平台 | ✅ | 绝对主流 |
2023年现状:
- 必须掌握:ES Module(100%项目使用)、CommonJS(Node.js开发)
- 了解即可:UMD(库开发兼容需求)、AMD(旧系统维护)
- 已淘汰:IIFE、CMD
最佳实践
-
新项目 :全面采用ES Module
html<script type="module" src="app.js"></script>
-
库开发:同时提供ES Module和UMD版本
-
Node.js:逐步迁移.mjs文件,混合使用过渡期
-
构建工具:统一用ES Module源码,通过Webpack/Rollup转换
未来趋势:
- 浏览器原生Import Maps普及
- ESM逐步替代CommonJS成为Node默认标准
- 基于ESM的Bundleless架构兴起(Vite、Snowpack)
CommonJS vs ES Module 核心区别解析
一、模块加载机制
特性 | CommonJS | ES Module |
---|---|---|
加载方式 | 同步加载(运行时) | 异步加载(编译时解析) |
执行时机 | 遇到require 立即执行 |
预解析依赖,延迟执行 |
动态加载 | 支持任意位置require() |
顶层静态import ,动态需用import() |
示例对比:
js
// CommonJS 动态加载
if (condition) {
const module = require('./moduleA');
}
// ES Module 报错(静态解析)
if (condition) {
import module from './moduleA'; // SyntaxError
}
二、模块导出本质
特性 | CommonJS | ES Module |
---|---|---|
导出类型 | 值的拷贝(副本) | 值的引用(实时绑定) |
导出可变性 | 导出后修改不影响已导入值 | 导出值变化会实时影响所有导入 |
默认导出 | module.exports = {} |
export default {} |
内存示意图:
css
CommonJS:
模块A exports → { count: 1 }(内存地址X)
模块B require得到副本 → { count: 1 }(内存地址Y)
ES Module:
模块A exports → { count: 1 }(内存地址X)
模块B import得到引用 → 指向内存地址X
三、静态分析与优化
特性 | CommonJS | ES Module |
---|---|---|
静态可分析性 | 动态特性难以静态分析 | 完全支持静态分析 |
Tree-shaking | 基本不可用 | 完美支持 |
循环依赖处理 | 可能得到未完成模块 | 通过实时绑定安全处理 |
Tree-shaking示例:
js
// math.js
export function add(a, b) { return a + b }
export function unused() { /*...*/ }
// app.js
import { add } from './math.js'
// 打包后 unused 函数会被自动移除
四、运行时特性
特性 | CommonJS | ES Module |
---|---|---|
顶层this指向 | 指向module.exports |
undefined |
严格模式 | 默认非严格模式 | 自动启用严格模式 |
变量污染 | 可能污染全局作用域 | 模块级作用域 |
五、环境支持
环境 | CommonJS | ES Module |
---|---|---|
Node.js | 原生支持(.cjs ) |
需文件后缀.mjs 或设置"type": "module" |
浏览器 | 需打包工具转换 | 原生支持(<script type="module"> ) |
Deno | 不支持 | 原生支持 |
六、循环依赖处理对比
CommonJS场景:
js
// a.js
exports.loaded = false;
const b = require('./b');
console.log('在a中,b.loaded =', b.loaded);
exports.loaded = true;
// b.js
exports.loaded = false;
const a = require('./a');
console.log('在b中,a.loaded =', a.loaded);
exports.loaded = true;
// 输出结果:
// 在b中,a.loaded = false
// 在a中,b.loaded = true
ES Module场景:
js
// a.mjs
import { bLoaded } from './b.mjs';
export let aLoaded = false;
console.log('在a中,b.loaded =', bLoaded);
aLoaded = true;
// b.mjs
import { aLoaded } from './a.mjs';
export let bLoaded = false;
console.log('在b中,a.loaded =', aLoaded);
bLoaded = true;
// 输出结果:
// 在b中,a.loaded = false
// 在a中,b.loaded = true
// (但后续访问的aLoaded/bLoaded都是更新后的值)
七、现代化演进
-
Node.js混合模式:
json// package.json { "type": "module", // 默认ESM "scripts": { "start": "node --experimental-require-module main.mjs" } }
-
浏览器直接加载ESM:
js<script type="module"> import { createApp } from 'https://unpkg.com/vue@3/dist/vue.esm-browser.js' </script>
-
构建工具统一处理(Webpack/Vite):
js// vite.config.js export default { build: { target: 'esnext' // 生成纯ESM代码 } }
总结选择策略
- 新项目:无脑选择ES Module
- Node.js库:同时提供CJS和ESM双版本
- 旧系统改造:逐步迁移策略
- 浏览器兼容 :现代浏览器直接使用ESM,旧浏览器通过
<script nomodule>
回退