RequireJS学习笔记
所谓模块化,是指应用程序由一组高度解耦的、存放在不同模块中的独特功能构成。
开源框架和ES6引入了多个JavaScript模块化系统:
- AMD(Asynchronous Module Definition,异步模块定义),RequireJS使用的模块化系统,浏览器优先
- CommonJS,Node.js使用的模块化系统,主要用于服务器端JS开发的模块化支持,同步化加载
- ES6模块化系统,语言级别的支持
模块系统仅仅是一套规范,浏览器并不理解这些规范。要加载模块,需要模块加载器的配合:
- RequireJS,理解AMD规范
- SystemJS,支持多种模块系统,包括AMD, CommonJS, ES6
- es6-module-loader:一个支持ES6模块的垫片库
- Browserify,让浏览器可以使用CommonJS模块系统
RequireJS是一个JavaScript文件和模块加载器,主要用于浏览器中,也支持Node.js之类的后端JavaScript运行环境。使用RequireJS有利于提高加载速度(异步)、帮助提高代码质量(模块化)。
RequireJS是AMD规范的实现,比起同步化的CommonJS规范,AMD更加适用于浏览器环境。
RequireJS支持绝大部分浏览器,包括IE 6.0。
可以到http://requirejs.org/docs/download.html下载RequireJS。
为了有效的利用RequireJS优化工具,你应当把所有内联的脚本移出HTML文件。并且,在HTML中仅仅引用require.js:
1 2 3 4 5 6 7 8 9 10 11 |
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>RequireJS Study</title> <!-- data-main提示RequireJS,在其自身被加载后,立即加载js/index.js文件 --> <script type="text/javascript" src="requirejs/require.js" data-main="js/index"></script> </head> <body> </body> </html> |
如果要避免RequireJS的加载动作阻塞UI渲染,可以把 <script> 标签放到 <body> 元素的后面。
上述的index.js,是应用程序的主脚本文件。 在此文件中,你可以使用RequireJS的API来加载任何其它你所需要的JavaScript文件:
1 2 3 4 5 6 7 8 9 10 |
console.log( 'loading index.js' ); // 加载目标JavaScript文件 // 使用模块标识符(module IDs)代替URL。模块标识符不需要加.js后缀,因为默认情况下RequireJS把所有文件都当做JS看待,此后缀会自动添加 requirejs( [ "utils/string-utils" ], function( util ) { /** * 此回调在js/utils/string-utils.js加载完毕后执行<br> * 如果string-utils.js中包含对define()的调用,那么此回调直到string-utils.js的依赖也被加载后,才执行 */ console.log( 'utils/string-utils.js loaded' ); } ); |
1 2 3 4 5 |
console.log( 'loading utils/string-utils.js' ); // 定义此文件的依赖 define( [ 'utils/utils' ], function() { console.log( 'utils/utils.js loaded' ); } ); |
1 |
console.log( 'loading utils/utils.js' ); |
打开浏览器访问index.html,控制台输出如下:
1 2 3 4 5 |
loading index.js loading utils/string-utils.js loading utils/utils.js utils/utils.js loaded utils/string-utils.js loaded |
可以很清晰的看出RequireJS加载脚本的顺序。
与传统的 <script> 标签不同,RequireJS采用了不同的脚本加载机制。
RequireJS总是基于 baseUrl 的相对路径加载新的JS,baseUrl通常是data-main属性指定的脚本所在目录 。data-main属性指定了脚本加载的起始点。调用下面的函数可以手工设置baseUrl:
1 2 3 |
require.config( { baseUrl : "/another/path" } ); |
如果既没有调用上述函数,也没有设置data-main属性,那么baseUrl和引用require.js的HTML的URL所在目录一致。
RequireJS默认假设所有依赖均为JavaScript脚本,因此将模块标识符转换为URL时,它会自动添加.js后缀。
如果模块标识符具有.js后缀、以/开头、或者包含http/https,则直接当做URL处理,不使用baseUrl + ModuleID + .js方式组装出URL。
RequireJS允许从不同位置加载模块:
1 2 3 4 5 6 7 8 |
requirejs.config( { // 默认的,模块从js/lib目录下加载 baseUrl : 'js/lib', paths : { // 对于app开头的模块标识符,则从js/app目录加载 app : '../app' } } ); |
data-main属性用来指定初始执行的模块标识符,此模块类似于传统编程语言的main函数,你可以在其中对RequireJS进行配置并加载第一个业务模块。
你不能假设此模块先于DOM中后续声明的JS加载和执行,原因是RequireJS默认为 <script> 标签添加了 async 属性。在data-main后声明其它脚本(额外的入口点)标签违背了其用意,data-main只适合单个入口点的情形。
模块通常和JS文件一一对应,但是模块可以声明依赖关系、并且避免全局NS污染。 通过依赖关系树,RequireJS允许并发的加载多个模块,提高速度。
包含两个变量声明的模块:
1 2 3 4 |
define( { name : "Alex", age : 30 } ); |
如果需要进行前置的初始化动作,可以使用函数式定义:
1 2 3 4 5 6 7 |
define( function() { //执行前置工作 return { name : "Alex", age: 30 } } ); |
注意:
- 模块初始化函数的返回值不一定是对象,任何合法的JavaScript函数返回值都是可以的,例如函数
- 遵循AMD规范的模块,其代码必须完全放置在模块初始化函数中,以避免全局NS的污染
可以在定义模块时声明其依赖,依赖加载完毕之前,模块初始化函数不会被调用:
1 2 3 4 5 6 |
define( [ './room', './dining-table' ], function() { return { name : "Alex", age : 30 } } ); |
依赖模块可以直接作为入参,注入到当前模块的初始化函数的入参中:
1 2 3 4 5 6 7 8 9 10 11 12 |
// app/room.js define( { no : '1103' } ); // app/person.js define( [ './room', './dining-table' ], function( room ) { return { name : "Alex", age : 30, roomNo : room.no, } } ); |
注意,入参注入的顺序,必须和模块依赖声明顺序一致。
你可以显式的命名模块:
1 2 3 4 5 6 |
define( 'app/person', [ './room', './dining-table' ], function() { return { name : "Alex", age : 30 } } ); |
通常不建议这样做,模块名称应当由优化工具根据JS文件名自动生成,这样可以增强可移植性并且利于多模块的打包。
可以将JSONP服务作为依赖声明,此时必须将JSONP的GET参数callback的值设置为define:
1 2 3 |
require( [ "http://gmem.cc/api/data.json?callback=define" ], function( data ) { // data就是JSONP返回的响应内容 } ); |
JSONP服务的返回值必须是JSON对象,数组、数字、字符串等不支持。
函数 requirejs.undef() 可以让RequireJS忘记已经定义的某个模块。该函数使用的前提是,目标模块尚未被引用。
有几种方式来指定RequireJS的配置项。首先,你可以在顶级HTML或者JS中调用 config() 函数:
1 2 3 4 5 |
<script src="scripts/require.js"></script> <script> require.config({ }); </script> |
在data-main入口点调用此函数也是可以的,但是要注意data-main脚本的异步加载特性。
另外,你可以在RequireJS加载前,在全局变量require中存放配置信息:
1 2 3 4 5 |
<script> var require = { }; </script> <script src="scripts/require.js"></script> |
配置项 | 说明 | ||||
baseUrl | 查找所有模块(除非匹配paths)对应文件时,使用的基础URL前缀。支持跨域加载 | ||||
paths | 用于映射不存放在baseUrl下面的模块,可以指定模块的多个备选加载位置:
|
||||
shim | 为那些非标准模块(没有使用define定义,且符合AMD规范)执行依赖和导出配置 | ||||
map | 执行模块标识符映射,用于从不同位置加载不同模块的依赖项,对于使用多个库版本的大型项目有用:
|
||||
config | 用于向某个模块传递(应用级别的)配置信息:
注意,当向一个包传递配置信息时,应该把包的主模块(Main module)作为键,而不是包ID:
|
||||
packages | 用于从CommonJS包中加载模块 | ||||
nodeIdCompat | Node.js将app.js和app看做同一个模块标识符,在RequireJS中默认不是。将此配置项设置为true,可以和Node.js兼容 | ||||
waitSeconds | 加载一个脚本的超时秒数,默认7。设置为0则永不超时 | ||||
context | 用于在同一个页面加载一个模块的多个版本 | ||||
deps | 声明一个依赖的数组,这些依赖在require被定义后,立即异步的加载 | ||||
callback | 当deps加载完毕后执行的回调函数 | ||||
enforceDefine | 如果设置为true,RequireJS发现加载的JS没有调用define()且不具有可供检查的shim导出字符串值时,会抛出异常 | ||||
xhtml | 如果设置为true,RequireJS使用document.createElementNS()来创建script元素 | ||||
urlArgs | RequireJS获取资源时,使用的额外URL参数,形式:
pname1=pval1&pname2=pval2 从2.2.0版本开始,此参数可以是一个函数:
|
||||
scriptType | 脚本的MIME类型,默认text/javascript | ||||
skipDataMain | 从2.1.9开始,设置为true,则不会尝试去寻找data-main属性。当RequireJS被嵌入到一个工具库中时有用 |
shim用于描述非标准模块的特征,你可以声明非标准模块的依赖项和“导出符号”。导出符号(exports)指出非标准模块中的哪一个全局变量代表此模块:
1 |
console.log( 'Module: Utils loaded.' ); |
1 2 3 4 5 6 7 8 9 10 11 12 |
console.log( 'Module: StringUtils loaded.' ); var StringUtils = { /** * * @param {String} str * @param {String} search */ indexOf: function ( str, search ) { return str ? str.indexOf( search ) : -1; } } var StringUtilsBase = {}; |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
require.config( { baseUrl: 'js', shim: { 'utils/StringUtils': { // 确保Utils.js先于StringUtils.js加载,尽管两者都不遵循AMD规范 deps: [ 'utils/Utils' ], // 在StringUtils.js中定义的全局变量StringUtils代表模块 exports: 'StringUtils' } } } ); require( [ 'utils/StringUtils' ], function ( SU ) { //SU == StringUtils,由于exports配置 console.log( StringUtils.indexOf ); console.log( SU.indexOf ); } ); |
上述代码运行结果如下:
1 2 3 4 |
Module: Utils loaded. Module: StringUtils loaded. function ( str, search ) { return str ? str.indexOf( search ) : -1; } function ( str, search ) { return str ? str.indexOf( search ) : -1; } |
你还可以指定init配置项,这是一个函数,其返回值代表模块:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
require.config( { baseUrl: 'js', shim: { 'utils/StringUtils': { init: function () { return { StringUtils: StringUtils, StringUtilsBase: StringUtilsBase } } } } } ); |
init和exports配置项冲突,不得联用。
RequireJS支持CommonJS的Package规范,可以从中加载模块。每个CommonJS包可以分配一个模块名称/前缀,对于每个包,其package配置可以指定以下属性:
- name:包的名称,用于模块名称/前缀映射
- location:包的位置,此URL相对于baseUrl,除非包含http/https或者以/开始
- main:包中的一个模块的名称,当某个脚本对包名进行require时,会自动require此“主模块”,默认值为main
package配置的示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
require.config( { //这里声明了两个保 packages: [ "cart", { name: "store", main: "store" //指定主模块 } ] } ); require( ['cart'],function(cart){} ); //cart/main.js会被加载 |
RequireJS遇到的错误一般是404,或者通信超时,RequireJ提供了几种错误处理方式:
- 在调用require时指定回调,举例:
1234567891011121314151617require( [ 'jquery' ], function ( $ ) {}, function ( err ) { //第二个参数是错误处理回调var failedId = err.requireModules && err.requireModules[ 0 ];//requireModules为失败的模块的列表if ( failedId === 'jquery' ) {// 取消jQuery的定义,重新配置和加载requirejs.undef( failedId );requirejs.config({paths: {jquery: 'local/jquery'}});require( [ 'jquery' ], function () {} );}} ); - 使用path配置,指定模块的备选加载路径
- 全局错误处理回调requirejs.onError,距离:
1234567requirejs.onError = function (err) {console.log(err.requireType);if (err.requireType === 'timeout') {console.log('modules: ' + err.requireModules);}throw err;};
RequireJS通过插件机制来扩展其功能,这些插件和require.js分开维护,需要单独下载。
用于加载文本资源,任何以 text! 开头的模块被作为纯文本看待,示例:
1 2 3 4 5 6 7 |
require( [ "some/module", "text!some/module.html", "text!some/module.css" ], function ( module, html, css ) { //变量html的内容是some/module.html //变量css的内容是some/module.css } ); |
text插件使用XHR来加载目标模块,注意CORS问题。
可以在页面加载完毕后执行特定的逻辑:
1 2 3 4 5 6 7 8 9 10 |
require( [ 'domReady' ], function ( domReady ) { domReady( function () { //这里的逻辑将在页面加载完毕后执行 } ); } ); //更加简洁的方式: require( [ 'domReady!' ], function ( doc ) { //这里的逻辑将在页面加载完毕后执行,入参是当前document对象 } ); |
没有任何区别,仅仅在require这个名称已经被使用的情况下,才需要使用requirejs。
Leave a Reply