4、AMD规范与RequireJS:异步模块定义
好,咱们进入第四章节。前面我们聊了CommonJS,那是服务端的神器。但到了浏览器端,情况就变了——文件加载是异步的,你不能等一个模块加载完再执行下一个。这时候,AMD规范就派上用场了。
AMD,全称是Asynchronous Module Definition,异步模块定义。说白了,它就是为浏览器量身定做的模块加载方案。我最早接触AMD是在2013年,那时候jQuery还是主流,RequireJS几乎是前端项目的标配。
define与require:核心用法
AMD规范里有两个核心方法:define和require。一个用来定义模块,一个用来加载模块。
define 的签名是这样的:
define(id?, dependencies?, factory);
- id:模块标识,可选。我个人习惯不写,让文件名决定。
- dependencies:依赖数组,可选。
- factory:工厂函数,返回模块的导出内容。
来看个例子:
// 定义一个加法模块
define('calculator/add', ['jquery'], function($) {
return function(a, b) {
return a + b;
};
});
require 的用法更直接:
require(['calculator/add'], function(add) {
console.log(add(1, 2)); // 3
});
嗯,这里要注意:require是异步的。它不会阻塞后续代码的执行。我在项目中遇到过有人把require当import用,结果拿到的值是undefined——回调函数还没执行呢。
配置文件详解
RequireJS的配置文件叫require.config。它就像项目的总开关,所有模块的路径、依赖关系都在这里定义。
require.config({
baseUrl: 'js/lib',
paths: {
'jquery': 'https://cdn.example.com/jquery-3.6.0.min',
'calculator': '../app/calculator'
},
shim: {
'backbone': {
deps: ['underscore', 'jquery'],
exports: 'Backbone'
}
}
});
配置项说明:
| 配置项 | 作用 | 我的建议 |
|---|---|---|
| baseUrl | 所有模块的根路径 | 统一管理,避免路径混乱 |
| paths | 模块别名映射 | CDN资源用这个,方便切换 |
| shim | 非AMD模块的兼容 | 老项目迁移时必备 |
| map | 模块版本映射 | 多版本共存时用 |
实战:用RequireJS重构计算器
好,理论说完了,咱们动手。还记得第三章那个计算器吗?现在用AMD规范重构它。
先看目录结构:
calculator/
├── index.html
├── js/
│ ├── main.js # 入口文件
│ ├── app/
│ │ ├── calculator.js # 主模块
│ │ ├── add.js # 加法
│ │ ├── subtract.js # 减法
│ │ ├── multiply.js # 乘法
│ │ └── divide.js # 除法
│ └── lib/
│ └── require.js # RequireJS库
第一步:定义各个运算模块
// js/app/add.js
define(function() {
return function(a, b) {
return a + b;
};
});
// js/app/subtract.js
define(function() {
return function(a, b) {
return a - b;
};
});
// 其他运算模块类似,不赘述
第二步:定义主模块
// js/app/calculator.js
define(['./add', './subtract', './multiply', './divide'],
function(add, subtract, multiply, divide) {
return {
add: add,
subtract: subtract,
multiply: multiply,
divide: divide
};
}
);
第三步:配置入口文件
// js/main.js
require.config({
baseUrl: 'js/app',
paths: {
'calculator': 'calculator'
}
});
require(['calculator'], function(calc) {
console.log('1 + 2 =', calc.add(1, 2));
console.log('5 - 3 =', calc.subtract(5, 3));
console.log('4 * 6 =', calc.multiply(4, 6));
console.log('10 / 2 =', calc.divide(10, 2));
});
第四步:在HTML中引入
<!DOCTYPE html>
<html>
<head>
<title>AMD计算器</title>
</head>
<body>
<script data-main="js/main" src="js/lib/require.js"></script>
</body>
</html>
避坑指南
我曾经在重构一个老项目时踩过几个坑,分享给你:
- 循环依赖:A依赖B,B又依赖A。RequireJS会报错。解决办法是提取公共模块。
- 路径问题:baseUrl和paths的优先级容易搞混。记住:paths会覆盖baseUrl。
- 缓存问题:开发时经常遇到改了代码但浏览器还是加载旧版本。可以在urlArgs配置里加个时间戳。
require.config({
urlArgs: 'bust=' + new Date().getTime()
});
嗯,这个技巧在调试时特别有用。但上线前记得去掉,不然用户每次访问都重新下载,性能就崩了。
AMD vs CommonJS:怎么选?
你可能会问:到底用哪个?我的经验是:
- 浏览器端项目:用AMD(RequireJS)或ES Module
- Node.js项目:用CommonJS
- 新项目:直接用ES Module,打包工具会处理
说白了,AMD是特定历史时期的产物。现在ES Module已经普及,但理解AMD能帮你更好地理解前端模块化的演进脉络。而且,很多老项目还在用RequireJS,你总会遇到的。
好,这一章就到这里。下一章我们聊聊CMD规范与Sea.js,看看另一种异步加载的思路。