手写 AMD:define 与 require 的回调依赖管理
本节目标
- 理解 AMD 的
define(id, deps, factory)与require(deps, cb)怎么工作 - 手写一个迷你 AMD 加载器,体会「回调式异步依赖」
- 对比它与 CJS 的「同步」差异
AMD 用「回调」处理异步加载的依赖:先声明依赖,依赖就绪再执行工厂。
// 运行环境:浏览器(或 Node 演示逻辑,这里给核心算法)
// 迷你 AMD 加载器核心:
const modules = {}
function define(id, deps, factory) {
modules[id] = { deps, factory, exports: {} }
}
const loading = {}
function require(ids, cb) {
const results = ids.map((id) => {
const m = modules[id]
// 真实 AMD 这里会去「异步下载并 eval」模块文件;简化为直接用已 define 的
return m.factory.apply(null, m.deps.map((d) => modules[d].exports))
})
cb.apply(null, results)
}
// 使用:
define('a', [], function () { return { name: 'A' } })
define('b', ['a'], function (a) { return { who: a.name + ' 的朋友' } })
require(['b'], function (b) { console.log(b.who) }) // 'A 的朋友'要点:AMD 的 require(deps, cb) 是「等依赖齐了再调 cb」——天然适配「依赖要从网络异步加载」的浏览器场景。这和 CJS「同步拿到」形成对比。
名词解释
define(id, deps, factory):AMD 的「声明模块」函数:给模块起 id、列依赖、写工厂(真正逻辑)。它把「依赖列表」显式写在参数里,加载器据此「先加载依赖、再执行工厂」。这是 AMD「依赖前置、异步就绪」的落地语法。
回调式依赖(Callback DI):AMD 不让你「直接拿到依赖」,而是「把工厂交给加载器,等依赖就绪它再调用你」。这种「反转控制」天然支持异步——依赖没下载完就先不调用,下载完一并注入。对比 CJS 的同步 require,AMD 为「浏览器网络加载」而生。
课后练习
练习 1:AMD 的 require(['b'], cb) 为什么适合浏览器?
答案:浏览器里模块要从网络下载,无法「同步拿到」。AMD 的回调机制「等 b 及其依赖都下载执行完,再调 cb 注入」,天然适配异步;而 CJS 的同步
require在浏览器原生不支持(会阻塞/拿不到)。这是 AMD 当年存在的核心理由。
练习 2:上面迷你加载器「简化为直接用已 define 的」漏了什么真实能力?
答案:漏了「按 id 去网络异步下载并 eval 模块文件」这一步——真实 AMD(RequireJS)会根据 id 拼 URL、发请求、拿到代码后
define注册、再触发等待中的回调。我们的简化版假设模块已 define,只为演示「依赖图 + 回调注入」的算法骨架。
总结
手写 AMD 加载器让你看清「异步依赖」怎么管——它把「我要什么」和「给我之后做什么」分开写:先 define 声明依赖,加载器等依赖就绪再调你的 factory 并注入依赖。对比 CJS 的「同步拿到」,AMD 为「依赖要异步下载」的浏览器而生。我的观点:今天新项目很少手写 AMD,但理解它让你能读懂老代码,也看清「依赖注入 + 回调」这套思想(其实和现代 Promise/async 一脉相承)。结论:模块系统的形态,永远被「运行环境的能力」塑造——浏览器当年没原生模块,于是有了 AMD 的异步回调;有了原生 ESM,AMD 就功成身退。