01|如果说第二篇讲的是“Node 世界终于有了秩序”,那第三篇要讲的就是:为什么浏览器没有乖乖照抄

第二篇讲到,CommonJS 最早先在 Node.js 里把模块化做成了真正可执行的制度。

那套制度为什么有力量?

因为它第一次把这些事情一起纳入了运行时秩序:

  • 每个文件是模块
  • 依赖应该声明
  • 接口应该导出
  • 运行时负责解析、缓存和拼装

这当然很强。

可历史真正有意思的地方,不在这里。

真正有意思的是:

既然 CommonJS 已经这么像答案了,为什么浏览器世界没有直接照抄?

很多人后来回看这段历史,会把 AMD 的出现写成一次多余分裂。

好像本来已经有了正确答案,前端社区偏要再造一个。

这其实写反了。

更接近真实的说法应该是:

浏览器没有照抄 CommonJS,不是因为它看不懂,而是因为它根本活在另一套现实里。

这就是第三篇真正要讲的起点。

AMD 不是为了和 CommonJS 抬杠才出现的。

它是浏览器世界被自己的物理条件逼出来的一次制度反击。


02|CommonJS 在 Node 里顺,不代表它在浏览器里也顺,因为浏览器模块加载首先是网络问题

这件事必须先讲透。

因为模块化之争里最容易被忽略的一层,就是:

Node 世界的模块加载,本质上更像文件系统问题;浏览器世界的模块加载,本质上首先是网络问题。

这差别非常大。

Node.js 里,require('./foo') 背后的想象是:

  • 文件就在本地
  • 读文件成本相对可控
  • 同步装载不会直接把用户卡在页面前
  • 启动阶段做一点同步工作是可以接受的

可浏览器不是这样。

浏览器一旦要加载一个模块,背后常常意味着:

  • 发请求
  • 等网络
  • 处理阻塞
  • 考虑首屏
  • 避免把页面体验拖死

这时如果你还想把 require() 当成一个“立刻返回结果”的同步动作,

问题马上就出来了。

因为浏览器没有答应你“模块一定已经在手边”。

它更常见的现实是:

模块还在路上。

这就是第三篇最关键的现实基础。

不是浏览器社区不喜欢 CommonJS 的写法。

而是它无法假装网络不存在。


03|所以浏览器侧真正想要的,不只是“有模块”,而是“有一种能和异步加载和平相处的模块制度”

这一步特别关键。

因为它解释了为什么 AMD 不是对模块化本身的背叛,反而是对模块化要求更完整之后的结果。

浏览器社区当时真正要解决的,不只是把代码拆文件。

他们还想一起解决这些问题:

  • 脚本不要再靠人工排顺序
  • 依赖关系要明确
  • 模块不要污染全局
  • 加载过程要适合浏览器

这最后一条,恰恰就是 Node 世界没那么敏感、浏览器世界却绕不开的点:

模块制度必须和异步加载兼容。

如果兼容不了,那你就只能在两种坏事里选一种:

  1. 要么继续靠 <script> 标签和手工顺序硬撑
  2. 要么同步加载,把页面和调试体验一起拖慢

所以 AMD 真正抓住的,不是“声明依赖”这么简单。

而是更硬的那个要求:

依赖应该被声明,而且声明方式必须允许加载器先把依赖异步取回来,再按正确顺序执行。

这是浏览器制度和 Node 制度真正分叉的地方。


04|RequireJS 为什么会变成这条路线最重要的代表?因为它不是空想,而是从 Dojo loader 的现实痛感里长出来的

如果你去看 James Burke 自己写的 RequireJS History,会发现这条路并不是纸上设计。

它是从很具体的浏览器加载痛感里长出来的。

Burke 之前做过很多 Dojo Loader 相关工作。

而那套经验让他非常清楚:

  • 同步 XHR 加载很别扭
  • eval 风格方案调试体验不好
  • 直接把一切都押在服务器端转译也会把前端开发门槛抬高

也就是说,RequireJS 最早瞄准的,并不是“做一个更学术正确的模块理论”。

它瞄准的是:

能不能给浏览器世界一套足够能跑、足够好调试、足够不依赖重型服务端前置处理的模块加载制度。

这点很重要。

因为它再次说明,JavaScript 的很多制度分叉都不是理念先打架。

而是:

环境先疼,社区才会长出新写法。

从这个角度看,RequireJS 很像浏览器模块制度史里的 Node.js

不是因为它们长得一样。

而是因为它们都先抓住了真实环境里的硬问题。


05|于是 AMD 真正给出的,不是“另一个 require”,而是 define 这套先声明依赖、后执行模块的浏览器合同

这一点一定要立住。

因为如果只从表面看,很多人会觉得:

AMD 不就是又搞了一套不同写法吗?

但它真正改掉的,其实是执行模型。

CommonJS 那边的直觉是:

代码执行到 require() 时,把依赖拿来,马上继续往下跑。

AMD 的核心想法是:

不要假设依赖已经在场。

先把模块定义出来。

先把依赖列表写清楚。

等加载器把这些依赖都搞定,再来执行 factory。

这就是 define([...], function (...) {}) 这套写法背后的真实意义。

它不是为了多包一层函数显得时髦。

而是为了把模块“注册”和“执行”拆开。

这样一来,浏览器加载器才有空间去做它必须做的事情:

  • 异步抓脚本
  • 并发请求
  • 按依赖顺序安排执行
  • 尽量不把全局搞脏

所以第三篇里最重要的一层不是 API 长什么样。

而是:

AMD 通过 define() 这层合同,第一次把浏览器的异步现实正式写进了模块制度本身。


06|这也就是为什么 James Burke 会反复强调:AMD 不是在唱反调,而是在说“浏览器就该按浏览器的现实来设计模块”

RequireJS 的官方文档其实讲得很直白。

它对 CommonJS 的态度并不是“那边全错了”。

它真正想说的是:

CommonJS 确实很有价值,

但它最初并没有完全拥抱浏览器里那些改变不了的现实:

  • network loading
  • inherent asynchronicity

这句话特别关键。

因为它把浏览器侧的立场讲得很清楚:

不是我们反对模块制度,而是我们反对一套假装浏览器不是浏览器的模块制度。

所以 AMD 的历史位置非常微妙。

它既吸收了 CommonJS 的一些优点:

  • 字符串 ID
  • 显式依赖
  • 模块值概念

又主动改掉了最不适合浏览器的那部分默认假设:

  • 同步拿依赖
  • 过重依赖运行时即时解析

这其实不是背叛。

这是本地化改造。

更准确一点说,是:

浏览器社区第一次把“我们这个环境的物理条件”升格成模块制度设计的起点。


07|所以 AMD 之所以会显得“啰嗦”,不是因为它笨,而是因为它把浏览器加载器原本偷偷干的事,提前写到了台面上

这是 AMD 最容易被误解的一层。

很多人今天看 define(['jquery'], function ($) {}),第一反应是:

太绕了。

不如 require() 直接。

这感觉当然可以理解。

但它背后其实对应的是一个真实交换:

你多写了一点样板,是为了让加载器少猜一点。

在浏览器里,如果依赖关系不提前说清楚,加载器就只能:

  • 扫描
  • 插桩
  • 借助额外转换

而这些办法都会带来新的问题:

  • 调试体验差
  • 工具要求高
  • 边界情况多
  • 对静态文件开发不友好

所以 AMD 看上去更“声明式”,并不是审美偏好。

而是浏览器世界做出的一个交换:

开发者多声明一点,加载器就能更稳、更早、更异步地把事情安排好。

这也是为什么 AMD 后来会被很多人描述成:

不一定是最漂亮的,

但在当时浏览器环境里,它是“最低阻力路径”。

这判断很重要。

因为它再次提醒你:

模块制度从来不是单纯拼语法手感。

它是在不同环境里,找最能活下来的折中。


08|可 AMD 的真正历史意义,还不只是在浏览器里能跑,它更重要的是:它证明了“浏览器原生没有模块”时,社区也能先造出一套接近制度级的现实

第三篇如果只写成“浏览器为什么要异步”,还是不够。

更值得记住的是,AMDRequireJS 一起证明了一件事:

即便语言标准和浏览器原生能力都还没到位,社区依然可以先造出一套足够稳定、足够可推广、足够能支撑大型前端项目的模块现实。

这非常重要。

因为它直接影响了后面好多年 JavaScript 世界的心理:

大家越来越习惯一件事:

标准可以慢,社区不会等。

只要现实已经够痛,

工具、规范、加载器、构建器就会先长出来。

AMD 的成名,本质上就是这种社区能力的一次大规模展示。

它让大家第一次看到:

  • 浏览器模块不是只能停留在技巧层
  • 它也可以被做成一种广泛传播的工程制度
  • 而且还能反过来影响后来标准层怎么想问题

这也是为什么 RequireJS 官方文档反复强调一个意思:

先在今天的浏览器里把可工作的模块系统跑起来,才能为未来的标准模块制度积累真实世界经验。

翻成人话就是:

先在现实里把它跑起来,未来标准才更有可能不脱离地面。


09|为什么 AMD 看起来像分裂,背后却是浏览器在自立门户

把前面这些线叠一起看,第三篇最重要的判断可以压成一句:

AMD 不是社区故意分裂,它是浏览器现实拒绝被 Node 规则统治后的制度自立。

这里的重点不在“拒绝”这两个字有多戏剧化。

而在于它说明:

JavaScript 模块化从来不是单中心演化。

CommonJS 先在 Node 里做成制度,并不自动意味着浏览器必须服从。

浏览器有自己的约束。

自己的性能问题。

自己的加载机制。

自己的调试需求。

当这些现实足够硬时,它就一定会反过来塑造模块 API。

AMD 正是这次反塑造最典型的产物。

它不是终局。

但它是一个非常关键的中途站。

因为没有它,浏览器世界对模块制度的要求就不会被说得这么明确。

后面的 bundler、ESM、甚至很多关于静态依赖和异步加载的讨论,也都会少掉一大块现实基础。


10|AMD 第一次把浏览器的异步现实写进了模块制度

模块化江湖的第三篇,最值得记住的,不是“define()require() 谁更好记”这种表层区别。

更值得记住的是:

AMD 出现时真正改变的,不只是写法,而是浏览器世界第一次把自己的异步加载现实,正面写进了模块制度。

RequireJS 最重要的历史贡献,也不只是“它曾经很流行”。

更是:

它让浏览器侧第一次拥有了一套不靠全局变量、不靠手工排 <script>、也不必强依赖服务端预处理,就能相对系统地组织模块依赖的现实方案。

所以第三篇如果只记一句,就记这句:

AMD 不是社区为了和 CommonJS 对着干才出现的,它是浏览器现实在说:如果模块制度要在这里活下去,那它就必须先学会和异步加载和平相处。


编者注(事实核对):文中关于 RequireJSAMD 的起源,主要依据 James Burke 在 RequireJS History 中对 Dojo loader、RunJSTransport/C、以及 AMD 形成过程的回顾。关于 AMD 的设计动机,主要依据 Why AMD?Why Web Modules? 中对浏览器网络加载、异步性、调试与无服务端预处理需求的说明。关于 AMD API 本身,主要依据 amdjs 维护的 AMD 规范文档,以及 CommonJS wiki 中保留的 Modules/AsynchronousDefinition 历史页面。正文将 AMD 概括为“浏览器现实对 Node 规则的一次制度反击”,属于基于其浏览器适配目标与后续历史影响做出的总结。


关键人物速览

  • James BurkeRequireJS 的发起者。第三篇里他代表的是“浏览器模块制度不能假装网络不存在”的那条线。
  • Kris ZypAMD API 形成过程中的关键人物之一。理解它怎样从 Transport/C 往真正的异步模块定义推进,绕不开他。
  • Tom Robinson:AMD 讨论过程中的重要参与者之一。理解这套 API 怎样在现实工具链与语法便利之间折中,他这条线很典型。

参考与延伸阅读

  1. RequireJS History
    https://requirejs.org/docs/history.html

  2. Why AMD?
    https://requirejs.org/docs/whyamd.html

  3. Why Web Modules?
    https://requirejs.org/docs/why.html

  4. RequireJS API
    https://requirejs.org/docs/api

  5. AMD API Specification
    https://github.com/amdjs/amdjs-api/blob/master/AMD.md

  6. CommonJS: Modules/AsynchronousDefinition
    https://wiki.commonjs.org/wiki/Modules/AsynchronousDefinition


下篇预告:bundler 为什么成了临时宪法。