require.js - 入门指南、进阶使用详解(附样例)
现在项目大都使用模块化开发,而 RequireJS 作为 AMD 模块开发的典范,还是很值得学习使用的。
目录
一、AMD 规范
1,AMD 基本介绍
- AMD 全称为 Asynchromous Module Definition(异步模块定义)
- AMD 是 RequireJS 在推广过程中对模块定义的规范化产出,它是一个在浏览器端模块化开发的规范。
- AMD 模式可以用于浏览器环境并且允许非同步加载模块,同时又能保证正确的顺序,也可以按需动态加载模块。
2,AMD 模块规范
- AMD 通过异步加载模块。模块加载不影响后面语句的运行。所有依赖某些模块的语句均放置在回调函数中。
- AMD 规范只定义了一个函数 define,通过 define 方法定义模块。该函数的描述如下:
define(id?, dependencies?, factory)
- id:指定义中模块的名字(可选)。如果没有提供该参数,模块的名字应该默认为模块加载器请求的指定脚本的名字。如果提供了该参数,模块名必须是“顶级”的和绝对的(不允许相对名字)。
- dependencies:当前模块依赖的,已被模块定义的模块标识的数组字面量(可选)。
- factory:一个需要进行实例化的函数或者一个对象。
- AMD 规范允许输出模块兼容 CommonJS 规范,这时 define 方法如下:
1 2 3 4 5 6 7 8 |
|
二、RequireJS 介绍
1,什么是 RequireJS
RequireJS 是一个 JavaScript 模块加载器。它非常适合在浏览器中使用,但它也可以用在其他脚本环境, 比如 Rhino 和 Node。使用 RequireJS 加载模块化脚本将提高代码的加载速度和质量。
2,使用 RequireJS 的好处
- 异步“加载”。使用 RequireJS,会在相关的 js 加载后执行回调函数,这个过程是异步的,所以它不会阻塞页面。
- 按需加载。通过 RequireJS,你可以在需要加载 js 逻辑的时候再加载对应的 js 模块,不需要的模块就不加载,这样避免了在初始化网页的时候发生大量的请求和数据传输。
- 更加方便的模块依赖管理。通过 RequireJS 的机制,你能确保在所有的依赖模块都加载以后再执行相关的文件,所以可以起到依赖管理的作用。
- 更加高效的版本管理。比如原来我们使用的 script 脚本引入的方式来引入一个 jQuery2.x 的文件,但如果有 100 个页面都是这么引用的,如果想换成 jQuery3.x,那你就不得不去改这 100 个页面。而使用 requireJS 只需要改一处地方,即修改 config 中 jQuery 的 path 映射即可。
- 当然还有一些诸如 cdn 加载不到 js 文件,可以请求本地文件等其它的优点,这里就不一一列举了。
三、RequireJS 的配置和使用
1,下载最新版的 require.js
下载地址:http://requirejs.org/docs/download.html
2,创建一个如下目录结构
(1)lib 文件夹下放置一些需要用到的 js 库,这里除了 require.js 外,还有 jquery。
(2)script 文件夹下放置 RequireJS 的入口 js、以及模块 js 文件。
(3)index.html 则为主页面。
3,效果图
(1)页面初始化的时候显示一个按钮。
(2)点击按钮会调用 hello 模块的方法,将信息显示在页面上。
4,代码说明
(1)index.html
1 2 3 4 5 6 7 8 9 10 11 12 |
|
注意 script 标签,除了指定 require.js 路径外,还有个 data-main 属性:
这属性指定在加载完 reuqire.js 后,就用 requireJS 加载该属性值指定路径下的 JS 文件并运行,所以一般该 JS 文件称为主 JS 文件(其 .js 后缀可以省略)。
(2)main.js
1 2 3 4 5 6 7 8 9 10 11 12 |
|
要改变 RequireJS 的默认配置,可以使用 require.configh 函数传入一个可选参数对象。下面是一些可以使用的配置:
- baseUrl:用于加载模块的根路径。在配置这个属性后,以后的文件都是在这个路径下查找内容了。
- paths:用于一些常用库或者文件夹路径映射,方便后面使用,省得每次都要输入一长串路径。(js 文件的后缀可以省略)
- shim:加载非 AMD 规范的 js,并解决其载入顺序。
require方法:
require 函数用于加载模块依赖,这里我们加载了 jQuery 以及 hello 这个自定义模块。在加载完毕的回调中,给按钮添加个点击事件,同时点击后会调用 hello 模块中的 showMessage 方法。
(3)hello.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
|
我们通过 define 方法定义一个 js 模块,并通过 return 对外暴露出接口(两个属性,一个方法)。同时该模块也是依赖于 jQuery。
四、require.configh 函数配置说明
要改变 RequireJS 的默认配置,可以使用 require.configh 函数传入一个可选参数对象。上面只演示了其中 baseUrl 和 paths 这两个配置,下面是一个完整的配置:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
|
1,baseUrl
用于加载模块的根路径。在配置这个属性后,以后的文件都是在这个路径下查找内容了。
2,paths
用于一些常用库或者文件夹路径映射,方便后面使用,省得每次都要输入一长串路径。(js 文件的后缀可以省略)
3,shim
虽然目前已经有一部分流行的函数库(比如 jQuery)符合 AMD 规范,但还有很多库并不符合。shim 就是为了加载这些非 AMD 规范的 js,并解决其载入顺序的。
比如上面样例,我们想通过 RequireJS 来使用 backbone,那么你就需要在配置中把它定义为一个 shim。同时通过 deps 配置其依赖关系,可以保证 underscore、jquery 先被加载。
shim配置的注意事项:
- shim 配置仅设置了代码的依赖关系,想要实际加载 shim 指定的或涉及的模块,仍然需要一个常规的 require/define 调用。设置 shim 本身不会触发代码的加载。
- 请仅使用其他"shim"模块作为 shim 脚本的依赖,或那些没有依赖关系,并且在调用 define() 之前定义了全局变量(如 jQuery 或 lodash )的 AMD 库。否则,如果你使用了一个 AMD 模块作为一个 shim 配置模块的依赖,在 build 之后,AMD 模块可能在 shim 托管代码执行之前都不会被执行,这会导致错误。终极的解决方案是将所有 shim 托管代码都升级为含有可选的 AMD define() 调用。
4,map
(1)对于给定的模块前缀,使用一个不同的模块 ID 来加载该模块。该手段对于某些大型项目很重要。
比如上面配置以后,不同的模块会使用不同版本的"foo":
- 当 some/newmodule 调用了 require('foo'),它将获取到 foo1.2.js 文件。
- 当 some/oldmodule 调用 require('foo'),时它将获取到 foo1.0.js 文件。
(2)map 还支持“*”,意思是“对于所有的模块加载,使用本 map 配置”。如果还有更细化的 map 配置,会优先于“*”配置。
比如下面配置,除了“some/oldmodule”外的所有模块,当要用“foo”时,都使用“foo1.2”来替代。
1 2 3 4 5 6 7 8 9 10 |
|
5,config
我们需要将配置信息传给一个模块。这些配置往往是 application 级别的信息,需要一个手段将它们向下传递给模块。这个通过 requirejs.config() 的 config 配置项就可以实现。
(1)可以通过加载特殊的依赖“module”来获取这些信息。
1 2 3 4 5 |
|
(2)也可通过符合 CommonJS 规范的模块获取
1 2 3 4 5 |
|
五、不同类型的模块定义
- 模块不同于传统的脚本文件,它良好地定义了一个作用域来避免全局名称空间污染。
- 它可以显式地列出其依赖关系,并以函数(定义此模块的那个函数)参数的形式将这些依赖进行注入,而无需引用全局变量。同时因为无需创建全局变量,甚至可以做到在同一个页面上同时加载同一模块的不同版本。
- RequireJS 的模块语法允许它尽快地加载多个模块,虽然加载的顺序不定,但依赖的顺序最终是正确的。
- 一个磁盘文件应该只定义 1 个模块。多个模块可以使用内置优化工具将其组织打包。
1,简单的键值对模块
(1)如果一个模块仅含键值对,没有任何依赖,则在 define() 中定义这些值对就好了
1 2 3 4 5 |
|
下面调用并测试这个模块
1 2 3 |
|
(2)下面还是一个简单的键值对模块,没有任何依赖,但需要做一些初始化 setup 工作。
1 2 3 4 5 6 7 8 9 10 |
|
2,只有一个主函数的模块
(1)没有任何依赖
1 2 3 4 5 6 |
|
下面调用并测试这个模块:
1 2 3 |
|
(2)存在相关依赖
1 2 3 4 5 6 |
|
下面调用并测试这个模块:
1 2 3 |
|
3,包含多个函数方法和变量的模块
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
|
下面调用并测试这个模块:
1 2 3 4 5 |
|
六 、相对路径的规则
不管是在配置中写路径还是直接在 require 函数中写路径,我们需要了解 requireJS 在不同情况下的相对路径。以下是相对路径的规则:
- 如果 <script/> 标签引入 require.js 时没有指定 data-main 属性,则以引入该 js 的 html 文件所在的路径为根路径。
- 如果有指定 data-main 属性,也就是有指定入口文件,则以入口文件所在的路径为根路径。
- 如果在 require.config() 中有配置 baseUrl,则以 baseUrl 的路径为根路径。
以上三条优先级逐级提升,如果有重叠,后面的根路径覆盖前面的根路径。
七、循环依赖问题解决
1,什么是循环依赖
(1)假设我们有如下 a、b 两个互相依赖的模块(a 依赖 b,b 同时依赖 a)
--- a.js ---
1 2 3 4 5 6 7 8 9 10 11 12 |
|
--- b.js ---
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
|
(2)我们如果调用 b 模块的 doSomething 方法
1 2 3 |
|
发现 b 调用 a 正常,但是 a 中调用 b 方法会报 undefined 错误。
2,问题解决
循环依赖比较罕见,它也是一个重构代码重新设计的警示灯。但不管怎样,有时候还是要用到循环依赖。对于循环依赖,只要依赖环中任何一条边是运行时依赖,这个环理论上就是活的。而如果全部边都是装载时依赖,这个环就是死的。
为避免循环依赖引发的问题,其实只要把一边改成运行时依赖就可以了,我们有如下几种方法。
方法1:使用 require() 去获取一个模块
我们对模块 a 进行如下修改,即不再依赖前置加载。而是通过引入 require 依赖,然后再通过 require() 方法去载入模块 b,并在回调中去执行。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
|
再次调用 b 模块的 doSomething 方法,可以发现运行成功了:
方法2:通过注入 exports 来解决
我们将模块 b 修改成如下代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
|
方法3:直接使用 CommonJS 规范定义模块
我们将模块 b 修改成如下代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
|
八、错误处理
一般我们使用 RequireJS 时碰到的错误主要是 404(未找到)错误、网络超时或加载的脚本含有错误。RequireJS 提供了如下三种方式来处理这些错误。
1,require 的错误回调函数
下面代码先用 jQuery 库的一个 CDN 版本,如果其加载出错,则切换到本地版本。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
|
2,通过"paths"数组配置
paths 配置项允许我们使用数组,下面代码先尝试加载 CDN 版本,如果出错,则退回到本地的 lib/jquery.js。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
|
3,全局的错误捕获:require.onError
为了捕获在局域的 errback 中未捕获的异常,可以重载 require.onError() 来实现全局的异常捕获。
1 2 3 4 5 6 7 |
|
原文出自:www.hangge.com 转载请保留原文链接:https://www.hangge.com/blog/cache/detail_1702.html
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现