模块化发展史:IIFE / AMD / CMD / UMD
本节目标
- 看懂 IIFE、AMD、CMD、UMD 各自解决什么时代的问题
- 理解「为什么有了它们还需要 ESM」
- 能识别老项目里这几种模式
模块化是「被需求一步步逼出来」的:
// 1) IAFE/IIFE:用函数作用域隔离(最原始)
var counter = (function () {
var n = 0
return { inc() { return ++n } } // 只暴露 inc,n 私有
})()
// 2) AMD(RequireJS):浏览器里「异步按需加载」模块
// define(['dep'], function (dep) { return {...} })
// 适合早期浏览器没有原生模块、又要按需加载大应用
// 3) CMD(SeaJS):就近依赖,写法更像 CommonJS
// define(function(require, exports){ var a = require('./a') })
// 4) UMD:一套代码同时兼容 AMD / CommonJS / 全局
// (function(root, factory){
// if (typeof define==='function'&&define.amd) define([], factory)
// else if (typeof module!=='undefined') module.exports = factory()
// else root.MyLib = factory()
// })(this, function(){ return {...} })为什么还需要 ESM:AMD/CMD 是「社区规范」,语法不统一、要运行时加载器;ESM 是语言标准,静态可分析、能被打包器优化(Tree Shaking),最终一统江湖。
名词解释
IIFE(立即执行函数表达式):(function(){...})() 这种「定义完立刻调用」的函数。它的价值是「制造一个独立作用域」,在 ES 模块出现前,是前端做「私有变量」的唯一办法。模块化史上的第一个台阶。
AMD / CMD / UMD:都是 ES 模块之前的社区规范。AMD 主打「浏览器异步加载」,CMD 主打「就近依赖写法」,UMD 主打「全兼容打包」。它们共同的历史使命是「在语言没原生模块时,先解决模块化」,现在基本被 ESM 取代,但老项目里仍常见,认得出即可。
课后练习
练习 1:UMD 为什么要同时判断 define.amd 和 module.exports?
答案:为了「一套源码到处用」:在 AMD 环境(有
define.amd)走 AMD 分支;在 Node/CommonJS 环境(有module)走 CJS 分支;都不在就挂到全局root上。UMD 是「最大兼容性」妥协产物。
练习 2:为什么「异步加载」是浏览器模块早期的核心需求?
答案:浏览器
<script>默认同步加载、阻塞页面;早期大应用若一次性同步加载所有模块,首屏极慢。AMD 的「按需异步加载」能让首屏只加载必要的,点哪加载哪——这是当时性能刚需,直到 ESM + 打包器普及才被更好解决。
总结
我的看法:学模块化发展史不是为了怀旧,而是为了看懂你接手的老项目。今天你大概率不会新写 AMD,但维护祖传代码时认得 define()、UMD 包裹,能少走很多弯路。更深的体会是:技术规范的演进永远是被「当时的痛点」推动的——IIFE 解决污染、AMD 解决加载、ESM 解决「统一+可优化」。理解每条规范的「它解决了什么」,比背语法有用。结论是:新项目无脑 ESM,老项目认规范、别慌。