news 2026/10/8 8:06:13

Learn X in Y minutes 深度解析:AMD 异步模块定义——从 define/require 到 require.js 与 r.js 构建优化

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Learn X in Y minutes 深度解析:AMD 异步模块定义——从 define/require 到 require.js 与 r.js 构建优化
  • 文档
  • 教程

【免费下载链接】learnxinyminutes-docs

Code documentation written as code! How novel and totally my idea!

项目地址:https://gitcode.com/gh_mirrors/le/learnxinyminutes-docs
点击查看免费下载

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 的两个核心约定:

  1. 依赖即参数:['awesomeAMD']中声明的依赖,会按顺序映射为工厂函数的形参awesomeAMD;
  2. 依赖先解析后执行:工厂函数只有在全部依赖就绪后才会被调用。

用 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!

项目地址:https://gitcode.com/gh_mirrors/le/learnxinyminutes-docs
点击查看免费下载
上一篇:KMS智能激活工具终极指南:5分钟永久解决Windows和Office激活难题
下一篇:AI语音转换的终极实战指南:检索机制驱动的商业级语音合成解决方案

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/8 8:03:41

text-to-cad实战:大模型驱动OpenSCAD与CadQuery生成可编辑三维模型

第一次接触到 text-to-cad 这个概念时&#xff0c;我正被一批杂七杂八的机械零件建模需求搞得焦头烂额——一个连接头要改法兰尺寸&#xff0c;一个支架要换螺栓孔位&#xff0c;电话里对方描述得眉飞色舞&#xff0c;我却得一句句猜着画。后来我开始尝试让程序直接听懂文字&am…

作者头像 李华
网站建设 2026/10/8 8:03:09

LLDAP 与 OneDev 集成指南:Generic LDAP 外部认证源完整配置

后端认证鉴权 【免费下载链接】lldap Light LDAP implementation 项目地址&#xff1a; https://gitcode.com/gh_mirrors/ll/lldap 点击查看 免费下载 本指南讲解如何将轻量级 LDAP 服务器 LLDAP 配置为 OneDev 的 Generic LDAP 外部认证源&#xff0c;实现用户使用 LLDAP 中的…

作者头像 李华
网站建设 2026/10/8 8:01:50

HarmonyOS 7 ArkUI:折叠屏鼠标悬停态与键盘焦点交互

ReviewBoard 在折叠屏连接鼠标后看起来很顺手&#xff1a;指针移到任务卡上&#xff0c;编辑、归档、更多操作会自然浮现。可一旦拔掉鼠标改用触控&#xff0c;这三个按钮就像从页面上消失了&#xff1b;接入键盘后&#xff0c;焦点虽然能移动&#xff0c;视觉高亮又和悬停态打…

作者头像 李华
网站建设 2026/10/8 8:01:01

Superpowers技能库详解:让Claude Code按工程方法论执行开发

先交代一下背景。最近在折腾 AI 编程助手的过程中&#xff0c;碰到一个叫Superpowers的开源技能库&#xff0c;热度挺高&#xff0c;GitHub 上 star 涨得很快&#xff0c;中文社区里问得最多的几个问题就是&#xff1a;它到底是什么、有哪些 skills、怎么引入这些技能、安装之后…

作者头像 李华
网站建设 2026/10/8 8:00:21

无人机集群分布式估计:事件触发与量化通信的EKF仿真对比

1. 为什么拿集中式EKF当标尺&#xff1a;分布式估计的通信代价从哪来无人机集群协同最核心的瓶颈其实不是算力&#xff0c;而是通信。你想想看&#xff0c;每架无人机都在用机载传感器&#xff08;惯导、GPS、视觉、雷达&#xff09;感知周围环境&#xff0c;但这些传感器数据如…

作者头像 李华