0115. 前端模块化发展史
- 1. 🎯 本节内容
- 2. 🫧 评价
- 3. 🤔 为什么要记录前端的模块化发展史?
- 4. 🤔 模块化的出现,核心是为了解决什么问题?
- 5. 🤔 模块化是什么?
- 6. 🤔 前端常见的模块化标准有哪些?
- 7. 🤔 前端模块化发展史分为哪四个阶段?
- 8. 🤔 CommonJS 规范在 nodejs 环境和浏览器环境有什么差异?
- 9. 🤔 AMD 和 CMD 是什么?为什么会出现?
- 10. 🔗 引用
1. 🎯 本节内容
- 前端模块化发展史
- AMD、CMD 的出现背景
- CommonJS 的适用场景
2. 🫧 评价
本节不是很重要,快速过一遍即可。
前端模块化发展史,简单讲就是一开始没必要,随着生态逐步完善,项目规模逐渐庞大,模块化自然而然就成为了必然。
如果对前端的发展史感兴趣的话,可以看看笔记中记录的 ⌛️ 四个发展阶段。
3. 🤔 为什么要记录前端的模块化发展史?
先大致地认识一下模块化的发展史,有助于更好的理解模块化。
多少可以帮助我们理解目前使用的这一套规范,是如何一步步演化而来的。
4. 🤔 模块化的出现,核心是为了解决什么问题?
模块化出现的目的就是为了解决开发复杂项目的一些痛点问题,如果没有模块化的话,我们在开发复杂应用的时候就会很痛苦。
如果你的项目只需要几个文件就能轻松解决,且代码量也不大的话,那么模块化就显得没那么必要了,在这种情况下还刻意细分模块,反而有点儿过渡设计的嫌疑。比如,就百来行的小 demo,让 AI 快速生成一个 .html 文件基本也就完事儿了。
5. 🤔 模块化是什么?
模块化就是不同的功能模块丢到不同的文件中,并且文件之间形成一种良好且清晰的依赖关系,模块之间不会造成污染。
模块化规定了复杂项目中的代码组织规范,约束我们必须按照指定的语法来导入、导出内容。
每一门能够开发大型应用的语言,都具有良好的模块化,比如 Java 语言中,使用“包”来实现,C# 语言叫动态链接库(命名空间),等等 ……
6. 🤔 前端常见的模块化标准有哪些?
- CommonJS(比较重要)
- AMD(不重要)
- CMD(不重要)
- ES6 模块化(重要)
1、2、3 之所以显得不那么重要,是因为在 2015 年,ECMAScript 官方推出了 ES6 模块化规范,这仨社区规范就显得不那么受待见了。
7. 🤔 前端模块化发展史分为哪四个阶段?
注意:⌛️ 四个阶段
下面笔记中介绍的前端模块化发展的四个阶段,是为了方便介绍才这么划分的,并非啥标准,是从网上东拼西凑整理而来的内容,你完全也可以根据自己所了解的历史大事件来划分不同的阶段。
7.1. ⌛️ 第一阶段 - JS 诞生
在 JavaScript 语言刚刚诞生的时候,它仅仅用于实现页面中的一些小效果,比如一些漂浮的小广告、鼠标 hover 时的字体颜色发生变化,等等。
在这个时候,一个页面所用到的 JS 可能只有区区几百行的代码,代码量并不多,模块化压根就没有出现的必要。
在这种情况下,语言本身所存在的一些缺陷往往被大家有意的忽略,因为程序的规模实在太小,只要开发人员小心谨慎一些,往往不会造成什么太大的问题。
在这个阶段,也不存在专业的前端工程师,由于前端要做的事情实在太少,因此这一部分工作往往由后端工程师顺带完成。
那个年代还没有前端工程师、后端工程师的说法,统一叫软件开发工程师。
第一阶段发生的大事件:
- 1996 年,NetScape 将 JavaScript 语言提交给欧洲的一个标准制定组织 ECMA(欧洲计算机制造商协会 European Computer Manufacturers Association)。
- 1998 年,NetScape 在与微软浏览器 IE 的竞争中失利,宣布破产,1999 年,它已被 AOL(America Online) 在 2017 年收购了。
7.2. ⌛️ 第二阶段 - ajax 出现
2005 年 ajax 出现,逐渐改变了 JavaScript 在浏览器中扮演的角色。
在这个阶段,前端不仅可以实现小的效果,还可以和服务器之间进行通信,以更好地体验来渲染页面内容,更好地跟用户进行交互。
JS 代码的数量开始逐渐增长,从最初的几百行,到后来的几万行,前端程序逐渐变得复杂。
后端开发者压力逐渐增加,这个阶段还将前端页面逻辑交由后端人员来全权处理已经不太现实了,致使一些公司开始招募专业的前端开发者。
那时,前端开发者的待遇远不及后端开发者,因为前端开发者承担的开发任务相对于后端开发来说,还是比较简单的,大多数人通过短短一个月的集训,就可以满足前端开发的需要。
究其根本原因,是因为前端开发还有几个大的问题没有解决,这些问题都严重的制约了前端程序的规模进一步扩大:
- 问题 1. 浏览器解释执行 JS 的速度太慢 👉 依赖浏览器内核优化
- 问题 2. 用户端的电脑配置不足 👉 依赖电脑配置优化
- 问题 3. 更多的代码带来了全局变量污染、依赖关系混乱等问题 👉 依赖 JS 语言基础 - 模块化机制
当时的浏览器主要用的都是 IE 浏览器,在那个年代,JS 跑在 IE 上的速度极慢,用户体验很不好。当时的 PC 配置和现在的 PC 配置没法比,1G 的内存在当时就算是大的了。在前面两个问题没有得到解决之前,第 3 个问题都没有思考的必要。
第三个问题在当时的解决方案很好理解,在开发一些大型应用的时候,公司会给不同的模块制定不同的命名规范,比如在变量的前边加上模块名和下划线 模块名_ 来解决。
立即执行函数
对于问题 3,你可能会想到使用立即执行函数来实现:“避免全局遍历污染的问题”。
如果使用立即执行函数的话,虽然可以把变量保存到局部,不影响全局。但是,这么做的话还是会存在新的问题,就是无法解决不同模块之间的联系问题 —— 比方说模块 A 需要用到模块 B 中定义的一个函数,因为模块 B 中的函数被定义到了局部,那模块 A 就无法获取到模块 B 中定义的函数了。
因此,立即执行函数并不能很好的解决当时 JS 在开发大型应用的第 3 点问题。
上面提到的这三个问题,就像是 阿喀琉斯之踵,成为前端开发挥之不去的阴影和原罪。
在这个阶段,前端开发处在一个非常尴尬的境地,它在「传统的开发模式」和「前后端分离模式」之间无助的徘徊。
第二阶段的大事件:
- IE 浏览器制霸市场后,几乎不再更新。
- ES4.0 流产,导致 JS 语言 10 年间几乎毫无变化。
- 2008 年 ES5 发布,仅解决了一些 JS API 不足的糟糕局面。
7.3. ⌛️ 第三阶段 - V8、NodeJS 出现
到了 2008 年,谷歌的 V8 引擎发布,将 JS 的执行速度推上了一个新的台阶,甚至可以和后端语言媲美。V8 引擎让 JavaScript 这一门解释型语言跑的跟编译型语言差不多一样快。解决了 JS 执行速度慢的问题(问题 1)。
摩尔定律持续发酵,个人电脑的配置开始飞跃。解决了电脑硬件配置的问题(问题 2)。
突然间,制约前端发展的两大问题得以解决,此时,只剩下最后一个问题还在负隅顽抗,即「全局变量污染和依赖混乱」的问题,解决了它,前端便可以突破一切障碍,未来无可限量。于是,全世界的前端开发者在社区中激烈的讨论,想要为这个问题寻求解决之道......
也是在 2008 年,有一个名叫 Ryan Dahl(NodeJS の 爹) 小伙子正在为一件事焦头烂额,他需要在服务器端手写一个高性能的 web 服务,该服务对于性能要求较高,当时市面上已有的 web 服务产品都满足不了需求。经过分析,他确定,如果要实现高性能,那么必须要尽可能的减少线程,而要减少线程,避免不了要使用异步的处理方案。一开始,他打算自己使用 C/C++ 语言来编写,可是这一过程实在太痛苦。就在他一筹莫展的时候,谷歌 V8 引擎的发布引起了他的注意,他突然发现,JS 不就是最好的实现 web 服务的语言吗?它天生就是单线程,并且是基于异步的!有了 V8 引擎的支撑,它的执行速度完全可以撑起一个服务器。而且 V8 是鼎鼎大名的谷歌公司发布的,谷歌一定会不断的优化 V8,有这种又省钱又省力的好事,我干嘛还要自己去写呢?于是,他基于开源的 V8 引擎,对源代码作了一些修改,便快速地完成了该项目。
2009 年,Ryan 推出了该 web 服务项目,命名为 nodejs。从此,JS 第一次堂堂正正的入住后端,不再是必须附属于浏览器的“玩具”语言了。也是从此刻开始,人们认识到,JS(ES)是一门真正的语言,它依附于运行环境(运行时 - 宿主程序)而执行。

NodeJS 的诞生,便把 JS 中的最后一个问题放到了台前,即全局变量污染和依赖混乱问题。要知道,NodeJS 是能跑在服务器端的,如果不解决这个问题,分模块开发就无从实现,而模块化是几乎所有复杂的后端程序必不可少的支持。经过社区的激烈讨论,最终,形成了一个模块化方案,即鼎鼎大名的 CommonJS,该方案,彻底解决了全局变量污染和依赖混乱的问题。该方案(CommonJS 规范)一出,立即被 NodeJS 支持,于是,NodeJS 成为了第一个为 JS 语言实现模块化的平台,为前端接下来的迅猛发展奠定了实践基础。
第三阶段的大事件:
- 2008 年,V8 发布。
- IE 的市场逐步被 firefox 和 chrome 蚕食,现已无力回天。
- 2009 年,nodejs 发布,并附带 commonjs 模块化标准。
7.4. ⌛️ 第四阶段
支持浏览器端的模块化规范 AMD、CMD、ES Module 陆续出炉 —— CommonJS 的出现打开了前端开发者的思路。既然后端可以使用模块化的 JS,作为 JS 语言的老东家浏览器为什么不行呢?于是,开始有人想办法把 CommonJS 运用到浏览器中。可是这里面存在诸多的困难(主要是由服务端与浏览器端的差异导致的),但办法总比困难多,有些开发者就想,既然 CommonJS 运用到浏览器困难,我们干嘛不自己重新定一个模块化的标准出来,难道就一定要用 CommonJS 标准吗?于是很快,AMD 规范出炉,它解决的问题和 CommonJS 一样,但是可以更好的适应浏览器环境。相继的,CMD 规范出炉,它对 AMD 规范进行了改进。这些行为,都受到了 ECMA 官方的密切关注...... 2015 年,ES6 发布,它提出了官方的模块化解决方案 —— ES6 模块化。
前端框架、后端框架、客户端框架、构建工具、测试工具、数据库驱动 …… 等一系列跟 JS 相关生态开始蓬勃发展。从此以后,模块化成为了 JS 本身特有的性质,这门语言终于有了和其他语言较量的资本,成为了可以编写大型应用的正式语言。
于此同时,很多开发者、技术厂商早已预见到 JS 的无穷潜力,于是有了下面的故事:
- 既然 JS 也能编写大型应用,那么自然也需要像其他语言那样有解决复杂问题的开发框架
- Angular、React、Vue 等前端开发框架出现
- Express、Koa 等后端开发框架出现
- 各种后端数据库驱动出现
- 要开发大型应用,自然少不了各种实用的第三方库的支持
- npm 包管理器出现,使用第三方库变得极其方便
- webpack 等构建工具出现,专门用于打包和部署
- 既然 JS 可以放到服务器环境,为什么不能放到其他终端环境呢?
- Electron 发布,可以使用 JS 语言开发桌面应用程序
- RN 和 Vuex 等技术发布,可以使用 JS 语言编写移动端应用程序
- 各种小程序出现,可以使用 JS 编写依附于其他应用的小程序
- 目前还有很多厂商致力于将 JS 应用到各种其他的终端设备,最终形成大前端生态
8. 🤔 CommonJS 规范在 nodejs 环境和浏览器环境有什么差异?
CommonJS 在 NodeJS 端表现良好,但无法适用于浏览器端。
核心差异:模块加载方式
- NodeJS 端:同步加载,本地文件 IO 速度快,不影响用户体验
- 浏览器端:同步加载导致页面卡死,用户体验极差
8.1. nodejs 环境
当使用 require(模块路径) 导入一个未缓存的模块时,NodeJS 会同步执行以下步骤:
- 根据模块路径找到本地文件,读取文件内容
- 将文件代码包装到函数中执行,返回
module.exports的值
require 函数会阻塞代码执行,直到模块完全加载和执行完毕。
这种同步机制在服务端是可行的,因为本地文件 IO 速度快,耗时可忽略不计。
8.2. 浏览器环境
将 CommonJS 应用到浏览器端面临以下挑战:
- 网络传输效率问题
- 浏览器需要通过网络从服务器下载 JS 文件
- 网络传输耗时远大于本地文件 IO
- 模块数量多时,下载耗时会严重影响性能
- 同步加载导致页面卡死
- CommonJS 的同步加载机制会阻塞浏览器渲染
- 在模块加载期间,页面会完全卡死,无法交互
- 浏览器厂商不支持
- 需要浏览器原生支持模块包装和执行机制
- CommonJS 是社区标准而非官方标准,浏览器厂商缺乏支持动力
要在浏览器中实现模块化,需要解决:
- 网络请求数量过多 👉 打包工具将模块合并为少量文件
- 同步加载阻塞页面 👉 异步加载方案(AMD、CMD)
- 缺乏官方标准 👉 ES Module 官方规范
在 ES Module 出现之前,社区推出了 AMD、CMD 规范来解决浏览器端模块化问题:
- 异步加载:避免阻塞页面渲染
- 手动包装:开发者自行将模块代码包装到函数中实现隔离
- 依赖管理:提供清晰的依赖声明和加载机制
这些社区方案有效地解决了浏览器模块化的核心问题,直到 2015 年 ES6 发布官方的 ES Module 标准。
9. 🤔 AMD 和 CMD 是什么?为什么会出现?
AMD 和 CMD 是两种异步模块加载方案,用于解决 CommonJS 在浏览器端的同步加载问题。
出现背景:
CommonJS 的同步加载机制依赖于本地文件系统的高速 IO,在服务端运行良好。但在浏览器端,模块需要通过网络请求获取,同步加载会阻塞页面渲染,导致页面卡死,用户体验极差。
AMD 和 CMD 的出现填补了 ES Module 标准发布之前,浏览器端模块化方案的空白。
核心区别:
- AMD (Asynchronous Module Definition)
- 代表实现:RequireJS
- 核心思想:依赖前置
- 加载机制:
- 定义模块时必须声明所有依赖
- 并行异步下载所有依赖
- 所有依赖加载并执行完成后,才执行当前模块
- CMD (Common Module Definition)
- 代表实现:Sea.js
- 核心思想:就近依赖
- 加载机制:
- 通过静态分析实现依赖预加载
- 按需执行,延迟执行依赖模块
历史地位:
虽然 AMD 和 CMD 在实现细节上有所不同,但都成功地将模块加载从同步转为异步,有效解决了 CommonJS 在浏览器端的核心痛点。
随着 ES6(2015)发布官方的 ES Module 标准,AMD 和 CMD 逐渐退出历史舞台。ES Module 结合了它们的优点,提供了更简洁的语法和更强大的功能,成为现代浏览器模块化的主流方案。