- 文档
- 教程
【免费下载链接】learnxinyminutes-docs
Code documentation written as code! How novel and totally my idea!
AMD(Asynchronous Module Definition,异步模块定义)是 JavaScript 模块化体系中最早面向浏览器异步加载场景的规范之一。本文以 learnxinyminutes-docs 仓库中收录的 AMD 教程(英文原版 及 印地语译本)为骨架,系统讲解define/require双方法模型、require.js 的真实世界用法,以及 r.js 的整项目构建优化流程。读完本文,你将能独立读懂并写出 AMD 风格模块,配置baseUrl与paths路径映射,并通过r.js将开发期分散的多文件模块打包为生产环境可用的单文件脚本。
AMD 是什么:为浏览器而生的异步模块规范
Asynchronous Module DefinitionAPI 规定了一种定义 JavaScript 模块的机制:模块自身及其依赖可以被异步加载。这一点对浏览器环境尤其重要——在浏览器中,模块的同步加载会带来四类典型问题:
- 性能(performance):同步加载会阻塞后续脚本解析与页面渲染;
- 可用性(usability):页面交互会被漫长的脚本下载阻塞;
- 调试(debugging):脚本加载顺序出错时,依赖缺失的错误极难定位;
- 跨域访问(cross-domain access):同步脚本引用受同源策略限制,难以从 CDN 或其他域加载模块。
AMD 正是为解决这些问题而设计的模块定义规范。整个 API 的核心极度精简——只有两个方法:define与require。所有模块的定义(definition)与消费(consumption)都围绕它们展开:
// `define(id?, dependencies?, factory)` 定义一个模块 // `require(dependencies, callback)` 导入一组依赖, // 并在传入的回调中消费它们基础概念:define 与 require 双方法模型
用 define 定义无依赖的具名模块
先看最基础的用法:用define定义一个具名模块(named module),它没有任何依赖,只需传入模块名和工厂函数(factory):
define('awesomeAMD', function(){ var isAMDAwesome = function(){ return true; }; // 工厂函数的返回值,就是其他模块或 require 调用 // 在请求 `awesomeAMD` 模块时接收到的导出值。 // 导出值可以是任何东西:构造函数、普通对象、 // 原始类型,甚至是 undefined(虽然这没什么用)。 return isAMDAwesome; });关键点在于:工厂函数的返回值即模块的导出值。无论你导出的是构造函数、对象、原始值还是undefined,其他模块拿到的就是这个值本身。
定义带依赖的模块
现在定义一个依赖awesomeAMD的新模块。注意define调用中多了一个参数——依赖数组:
define('loudmouth', ['awesomeAMD'], function(awesomeAMD){ // 依赖会按照声明顺序依次传给工厂函数的形参 var tellEveryone = function(){ if (awesomeAMD()){ alert('This is sOoOo rad!'); } else { alert('Pretty dull, isn\'t it?'); } }; return tellEveryone; });这里体现了 AMD 的两个核心约定:
- 依赖即参数:
['awesomeAMD']中声明的依赖,会按顺序映射为工厂函数的形参awesomeAMD; - 依赖先解析后执行:工厂函数只有在全部依赖就绪后才会被调用。
用 require 启动程序
知道了如何定义模块,接下来用require启动程序。require的签名是(arrayOfDependencies, callback):
require(['loudmouth'], function(loudmouth){ loudmouth(); });require负责导入依赖集合,并把它们作为参数传入回调。当页面加载完成后,loudmouth被调用,弹出 'This is sOoOo rad!'。
一个极简的 AMD 实现:30 行看懂原理
为了让教程真正"可运行",原文档当场实现了一个非异步的 AMD 精简版本。这个实现虽然只有两个函数,却完整揭示了 AMD 的底层机制——模块注册表 + 依赖注入:
function define(name, deps, factory){ // 注意无依赖模块的处理方式: // 没有 factory 时,第二个参数(deps)本身就是工厂函数 define[name] = require(factory ? deps : [], factory || deps); } function require(deps, callback){ var args = []; // 首先取出 require 调用所需的全部依赖 for (var i = 0; i < deps.length; i++){ args[i] = define[deps[i]]; } // 满足回调的全部依赖后,注入并调用 return callback.apply(null, args); }这段代码揭示了 AMD 的实质:
define把模块名作为键、工厂函数的执行结果作为值,存入模块注册表;require遍历依赖数组,从注册表中按名取回已解析的模块,再通过callback.apply(null, args)把依赖注入回调;- 无依赖模块的巧妙处理:
factory ? deps : [],即没有工厂函数时直接取deps作为工厂。
这也是官方规范(amdjs/amdjs-api)所描述的通用模型:一个模块注册表 + 一个依赖解析注入器。真实加载器(如 require.js)只是在这个模型之上补上了异步加载层。
实战:require.js 中的真实世界用法
XHR 异步加载:AMD 中的 A 被真正实现
与上面的入门示例不同,require.js(最流行的 AMD 库)真正实现了 AMD 中的A——通过 XHR 异步加载模块及其依赖:
/* file: app/main.js */ require(['modules/someClass'], function(SomeClass){ // 回调会被延迟执行,直到依赖加载完成 var thing = new SomeClass(); }); console.log('So here we are, waiting!'); // 这行会先执行注意执行顺序:console.log在回调之前执行。这正是异步加载的直观证据——require发出 XHR 请求后立刻返回,页面脚本继续往下走;当modules/someClass从服务器返回并解析完毕后,回调才被触发。
约定优于配置:一文件一模块,路径即模块名
按照惯例,通常一个文件存放一个模块。require.js 能基于文件路径解析模块名,因此你不需要给模块命名,直接用文件位置引用即可。以下面目录结构为例,someClass被假定位于baseUrl配置所指向的modules文件夹中:
app/ main.js modules/ someClass.js someHelpers.js ... daos/ things.js ...这意味着我们可以不指定模块 id就定义someClass:
/* file: app/modules/someClass.js */ define(['daos/things', 'modules/someHelpers'], function(thingsDao, helpers){ // 模块定义同样是异步完成的 function SomeClass(){ this.method = function(){/**/}; // ... } return SomeClass; });依赖数组里的'daos/things'、'modules/someHelpers'都是相对baseUrl的路径,加载器会据此发起 XHR 请求。
requirejs.config:自定义路径映射
要修改默认的路径映射行为,在main.js中调用requirejs.config(configObj):
/* file: main.js */ requirejs.config({ baseUrl : 'app', paths : { // 也可以从其他位置加载模块 jquery : '//ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min', coolLibFromBower : '../bower_components/cool-lib/coollib' } }); require(['jquery', 'coolLibFromBower', 'modules/someHelpers'], function($, coolLib, helpers){ // `main` 文件必须至少调用一次 require, // 否则任何代码都不会被执行 coolLib.doFancyStuffWith(helpers.transform($('#foo'))); });配置项语义如下:
| 配置项 | 作用 | 示例值 |
|---|---|---|
baseUrl | 所有模块路径解析的基准目录 | 'app' |
paths | 为模块 id 指定实际加载路径;可用完整 URL(如 CDN)或相对baseUrl的路径 | jquery : '//ajax.googleapis.com/...'、coolLibFromBower : '../bower_components/...' |
特别值得注意paths的两种取值:
- 跨域 URL:
jquery直接指向 Google CDN 的压缩包路径(省略.js后缀),异步加载天然绕开了同步脚本的跨域限制; - 本地相对路径:
coolLibFromBower指向baseUrl之外的 bower 组件目录,用../跳出app目录。
data-main:单一入口自动加载
基于 require.js 的应用通常只有一个入口(main.js),它作为data-main属性传给require.js的 script 标签,页面加载时会自动加载并执行:
<!DOCTYPE html> <html> <head> <title>A hundred script tags? Never again!</title> </head> <body> <script src="require.js">$ npm install requirejs -g然后用配置文件喂给它:
$ r.js -o app.build.js-o表示 optimizer 模式,app.build.js即构建配置文件。
app.build.js 配置详解
针对上文示例,构建配置大致如下:
/* file : app.build.js */ ({ name : 'main', // 入口模块的名称 out : 'main-built.js', // 输出文件的名称 baseUrl : 'app', paths : { // `empty:` 告诉 r.js:这个模块仍然从 CDN 加载, // 使用 main.js 中指定的位置,不要打进构建产物 jquery : 'empty:', coolLibFromBower : '../bower_components/cool-lib/coollib' } })| 配置项 | 含义 |
|---|---|
name | 入口模块名,即依赖图分析的起点 |
out | 构建产物文件名 |
baseUrl | 与main.js中的baseUrl保持一致,供模块路径解析 |
paths | 与运行时配置对应;其中empty:前缀是关键技巧 |
empty:前缀的语义值得单独强调:以jquery : 'empty:'为例,r.js 会跳过该依赖的本地解析与合并,构建产物中保留对main.js指定 CDN 地址的引用。这样既避免了把体积庞大的第三方库打进产物,又保持了"开发期引用路径 = 生产期引用路径"的一致性。
生产环境切换:只改一行><script src="require.js">赞
- 文档
- 教程
【免费下载链接】learnxinyminutes-docs
Code documentation written as code! How novel and totally my idea!
相关推荐
学习 AMD 异步模块定义:从 define/require 到 require.js 与 r.js 构建实战
学习 AMD 异步模块定义:从 define/require 到 require.js 与 r.js 构建实战 导读 本篇技术指南以本仓库根目录的 amd.md
文档教程AMD 异步模块定义实战指南:基于 learnxinyminutes-docs 的 define/require 与 require.js 全解析
AMD 异步模块定义实战指南:基于 learnxinyminutes docs 的 define/require 与 require.js 全解析 本指南以仓库
文档教程Learn X in Y Minutes 项目文档
Learn X in Y Minutes 项目文档 1. 项目目录结构及介绍 learnxinyminutes docs 项目是一个开源文档项目,旨在为各种编程
文档教程