JS -- 一篇文章掌握RequireJS经常使用知识

本文采起按部就班的方式,从理论到实践,从RequireJS官方API文档中,总结出在使用RequireJS过程当中最经常使用的一些用法,并对文档中不够清晰具体的内容,加以例证和分析,分享给你们供你们参考,具体内容以下javascript

1. 模块化
相信每一个前端开发人员在刚开始接触js编程时,都写过相似下面这样风格的代码:css

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<script type= "text/javascript" >
   var a = 1;
   var b = 2;
   var c = a * a + b * b;
 
   if (c> 1) {
     alert( 'c > 1' );
   }
 
   function add(a, b) {
     return a + b;
   }
 
   c = add(a,b);
</script>
?
1
< a href = "javascript:;" onclick = "click(this);" title = "" >请点击</ a >

这些代码的特色是:html

  • 处处可见的全局变量
  • 大量的函数
  • 内嵌在html元素上的各类js调用

固然这些代码自己在实现功能上并无错误,可是从代码的可重用性,健壮性以及可维护性来讲,这种编程方式是有问题的,尤为是在页面逻辑较为复杂的应用中,这些问题会暴露地特别明显:前端

  • 全局变量极易形成命名冲突
  • 函数式编程很是不利于代码的组织和管理
  • 内嵌的js调用很不利于代码的维护,由于html代码有的时候是十分臃肿和庞大的

因此当这些问题出现的时候,js大牛们就开始寻找去解决这些问题的究极办法,因而模块化开发就出现了。正如模块化这个概念的表面意思同样,它要求在编写代码的时候,按层次,按功能,将独立的逻辑,封装成可重用的模块,对外提供直接明了的调用接口,内部实现细节彻底私有,而且模块之间的内部实如今执行期间互不干扰,最终的结果就是能够解决前面举例提到的问题。一个简单遵循模块化开发要求编写的例子:html5

?
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
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
//module.js
var student = function (name) {
     return name && {
         getName: function () {
           return name;
         }
       };
   },
   course = function (name) {
     return name && {
         getName: function () {
           return name;
         }
       }
   },
   controller = function () {
     var data = {};
     return {
       add: function (stu, cour) {
         var stuName = stu && stu.getName(),
           courName = cour && cour.getName(),
           current,
           _filter = function (e) {
             return e === courName;
           };
 
         if (!stuName || !courName) return ;
 
         current = data[stuName] = data[stuName] || [];
 
         if (current.filter(_filter).length === 0) {
           current.push(courName);
         }
       },
       list: function (stu) {
         var stuName = stu && stu.getName(),
           current = data[stuName];
         current && console.log(current.join( ';' ));
       }
     }
   };
 
//main.js
 
var stu = new student( 'lyzg' ),
   c = new controller();
 
c.add(stu, new course( 'javascript' ));
c.add(stu, new course( 'html' ));
c.add(stu, new course( 'css' ));
c.list(stu);

以上代码定义了三个模块分别表示学生,课程和控制器,而后在main.js中调用了controller提供的add和list接口,为lyzg这个学生添加了三门课程,而后在控制台显示了出来。运行结果以下:java

?
1
javascript;html;css

经过上例,能够看出模块化的代码结构和逻辑十分清晰,代码看起来十分优雅,另外因为逻辑都经过模块拆分,因此达到了解耦的目的,代码的功能也会比较健壮。不过上例使用的这种模块化开发方式也并非没有问题,这个问题就是它仍是把模块引用如student这些直接添加到了全局空间下,虽然经过模块减小了不少全局空间的变量和函数,可是模块引用自己仍是要依赖全局空间,才能被调用,当模块较多,或者有引入第三方模块库时,仍然可能形成命名冲突的问题,因此这种全局空间下的模块化开发的方式并非最完美的方式。目前常见的模块化开发方式,全局空间方式是最基本的一种,另外常见的还有遵循AMD规范的开发方式,遵循CMD规范的开发方式,和ECMAScript 6的开发方式。须要说明的是,CMD和ES6跟本文的核心没有关系,因此不会在此介绍,后面的内容主要介绍AMD以及实现了AMD规范的RequireJS。node

2. AMD规范
正如上文提到,实现模块化开发的方式,另外常见的一种就是遵循AMD规范的实现方式,不过AMD规范并非具体的实现方式,而仅仅是模块化开发的一种解决方案,你能够把它理解成模块化开发的一些接口声明,若是你要实现一个遵循该规范的模块化开发工具,就必须实现它预先定义的API。好比它要求在加载模块时,必须使用以下的API调用方式:jquery

?
1
2
3
4
require([module], callback)
其中:
[module]:是一个数组,里面的成员就是要加载的模块;
callback:是模块加载完成以后的回调函数

全部遵循AMD规范的模块化工具,都必须按照它的要求去实现,好比RequireJS这个库,就是彻底遵循AMD规范实现的,因此在利用RequireJS加载或者调用模块时,若是你事先知道AMD规范的话,你就知道该怎么用RequireJS了。规范的好处在于,不一样的实现却有相同的调用方式,很容易切换不一样的工具使用,至于具体用哪个实现,这就跟各个工具的各自的优势跟项目的特色有关系,这些都是在项目开始选型的时候须要肯定的。目前RequireJS不是惟一实现了AMD规范的库,像Dojo这种更全面的js库也都有AMD的实现。ajax

最后对AMD全称作一个解释,译为:异步模块定义。异步强调的是,在加载模块以及模块所依赖的其它模块时,都采用异步加载的方式,避免模块加载阻塞了网页的渲染进度。相比传统的异步加载,AMD工具的异步加载更加简便,并且还能实现按需加载,具体解释在下一部分说明。编程

3. JavaScript的异步加载和按需加载
html中的script标签在加载和执行过程当中会阻塞网页的渲染,因此通常要求尽可能将script标签放置在body元素的底部,以便加快页面显示的速度,还有一种方式就是经过异步加载的方式来加载js,这样能够避免js文件对html渲染的阻塞。

第1种异步加载的方式是直接利用脚本生成script标签的方式:

?
1
2
3
4
5
6
7
8
( function () {
   var s = document.createElement( 'script' );
   s.type = 'text/javascript' ;
   s.async = true ;
   var x = document.getElementsByTagName( 'script' )[0];
   x.parentNode.insertBefore(s, x);
})();

这段代码,放置在script标记内部,而后该script标记添加到body元素的底部便可。

第2种方式是借助script的属性:defer和async,defer这个属性在IE浏览器和早起的火狐浏览器中支持,async在支持html5的浏览器上都支持,只要有这两个属性,script就会以异步的方式来加载,因此script在html中的位置就不重要了:

?
1
2
3
<script defer async= "true" type= "text/javascript" src= "app/foo.js" ></script>
<script defer async= "true" type= "text/javascript" src= "app/bar.js" ></script>
<script defer async= "true" type= "text/javascript" src= "app/main.js" ></script>

这种方式下,全部异步js在执行的时候仍是按顺序执行的,否则就会存在依赖问题,好比若是上例中的main.js依赖foo.js和bar.js,可是main.js先执行的话就会出错了。虽然历来理论上这种方式也算不错了,可是不够好,由于它用起来很繁琐,并且还有个问题就是页面须要添加多个script标记以及没有办法彻底作到按需加载。

JS的按需加载分两个层次,第一个层次是只加载这个页面可能被用到的JS,第二个层次是在只在用到某个JS的时候才去加载。传统地方式很容易作到第一个层次,可是不容易作到第二个层次,虽然咱们能够经过合并和压缩工具,将某个页面全部的JS都添加到一个文件中去,最大程度减小资源请求量,可是这个JS请求到客户端之后,其中有不少内容可能都用不上,要是有个工具可以作到在须要的时候才去加载相关js就完美解决问题了,好比RequireJS。

4. RequireJS经常使用用法总结
前文屡次说起RequireJS,本部分将对它的经常使用用法详细说明,它的官方地址是:http://www.requirejs.cn/,你能够到该地址去下载最新版RequireJS文件。RequireJS做为目前使用最普遍的AMD工具,它的主要优势是:

  • 彻底支持模块化开发
  • 能将非AMD规范的模块引入到RequireJS中使用
  • 异步加载JS
  • 彻底按需加载依赖模块,模块文件只须要压缩混淆,不须要合并
  • 错误调试
  • 插件支持

4.01 如何使用RequireJS
使用方式很简单,只要一个script标记就能够在网页中加载RequireJS:

?
1
<script defer async= "true" src= "/bower_components/requirejs/require.js" ></script>

因为这里用到了defer和async这两个异步加载的属性,因此require.js是异步加载的,你把这个script标记放置在任何地方都没有问题。

4.02 如何利用RequireJS加载并执行当前网页的逻辑JS

4.01解决的仅仅是RequireJS的使用问题,但它仅仅是一个JS库,是一个被当前页面的逻辑所利用的工具,真正实现网页功能逻辑的是咱们要利用RequireJS编写的主JS,这个主JS(假设这些代码都放置在main.js文件中)又该如何利用RJ来加载执行呢?方式以下:

?
1
<script data-main= "scripts/main.js" defer async= "true" src= "/bower_components/requirejs/require.js" ></script>

对比4.01,你会发现script标记多了一个data-main,RJ用这个配置当前页面的主JS,你要把逻辑都写在这个main.js里面。当RJ自身加载执行后,就会再次异步加载main.js。这个main.js是当前网页全部逻辑的入口,理想状况下,整个网页只须要这一个script标记,利用RJ加载依赖的其它文件,如jquery等。

 4.03 main.js怎么写
假设项目的目录结构为:

main.js是跟当前页面相关的主JS,app文件夹存放本项目自定义的模块,lib存放第三方库。

html中按4.02的方式配置RJ。main.js的代码以下:

?
1
2
3
require([ 'lib/foo' , 'app/bar' , 'app/app' ], function (foo, bar, app) {
   //use foo bar app do sth
});

在这段JS中,咱们利用RJ提供的require方法,加载了三个模块,而后在这个三个模块都加载成功以后执行页面逻辑。require方法有2个参数,第一个参数是数组类型的,实际使用时,数组的每一个元素都是一个模块的module ID,第二个参数是一个回调函数,这个函数在第一个参数定义的全部模块都加载成功后回调,形参的个数和顺序分别与第一个参数定义的模块对应,好比第一个模块时lib/foo,那么这个回调函数的第一个参数就是foo这个模块的引用,在回调函数中咱们使用这些形参来调用各个模块的方法,因为回调是在各模块加载以后才调用的,因此这些模块引用确定都是有效的。

从以上这个简短的代码,你应该已经知道该如何使用RJ了。

4.04 RJ的baseUrl和module ID
在介绍RJ如何去解析依赖的那些模块JS的路径时,必须先弄清楚baseUrl和module ID这两个概念。

html中的base元素能够定义当前页面内部任何http请求的url前缀部分,RJ的baseUrl跟这个base元素起的做用是相似的,因为RJ老是动态地请求依赖的JS文件,因此必然涉及到一个JS文件的路径解析问题,RJ默认采用一种baseUrl + moduleID的解析方式,这个解析方式后续会举例说明。这个baseUrl很是重要,RJ对它的处理遵循以下规则:

  • 在没有使用data-main和config的状况下,baseUrl默认为当前页面的目录
  • 在有data-main的状况下,main.js前面的部分就是baseUrl,好比上面的scripts/
  • 在有config的状况下,baseUrl以config配置的为准

上述三种方式,优先级由低到高排列。

data-main的使用方式,你已经知道了,config该如何配置,以下所示:

?
1
2
3
require.config({
   baseUrl: 'scripts'
});

这个配置必须放置在main.js的最前面。data-main与config配置同时存在的时候,以config为准,因为RJ的其它配置也是在这个位置配置的,因此4.03中的main.js能够改为以下结构,以便未来的扩展:

?
1
2
3
4
5
6
7
require.config({
   baseUrl: 'scripts'
});
 
require([ 'lib/foo' , 'app/bar' , 'app/app' ], function (foo, bar, app) {
   // use foo bar app do sth
});

关于module ID,就是在require方法以及后续的define方法里,用在依赖数组这个参数里,用来标识一个模块的字符串。上面代码中的['lib/foo', 'app/bar', 'app/app']就是一个依赖数组,其中的每一个元素都是一个module ID。值得注意的是,module ID并不必定是该module 相关JS路径的一部分,有的module ID很短,但可能路径很长,这跟RJ的解析规则有关。下一节详细介绍。

4.05 RJ的文件解析规则
RJ默认按baseUrl + module ID的规则,解析文件,而且它默认要加载的文件都是js,因此你的module ID里面能够不包含.js的后缀,这就是为啥你看到的module ID都是lib/foo, app/bar这种形式了。有三种module ID,不适用这种规则:

假如main.js以下使用:

?
1
2
3
4
5
6
7
require.config({
   baseUrl: 'scripts'
});
 
require([ '/lib/foo' , 'test.js' , '/js/jquery' ], function (foo, bar, app) {
   // use foo bar app do sth
});

这三个module 都不会根据baseUrl + module ID的规则来解析,而是直接用module ID来解析,等效于下面的代码:

?
1
2
3
<script src= "/lib/foo.js" ></script>
<script src= "test.js" ></script>
<script src= "/js/jquery.js" ></script>

各类module ID解析举例:

例1,项目结构以下:

main.js以下:

?
1
2
3
4
5
6
7
require.config({
   baseUrl: 'scripts'
});
 
require([ 'lib/foo' , 'app/bar' , 'app/app' ], function (foo, bar, app) {
   // use foo bar app do sth
});

baseUrl为:scripts目录

moduleID为:lib/foo, app/bar, app/app

根据baseUrl + moduleID,以及自动补后缀.js,最终这三个module的js文件路径为:

?
1
2
3
scripts/lib/foo.js
scripts/app/bar.js
scripts/app/app.js

例2,项目结构同例1:

main.js改成:

?
1
2
3
4
5
6
7
8
9
10
11
require.config({
   baseUrl: 'scripts/lib' ,
   paths: {
    app: '../app'
   }
});
 
 
require([ 'foo' , 'app/bar' , 'app/app' ], function (foo, bar, app) {
   // use foo bar app do sth
});

这里出现了一个新的配置paths,它的做用是针对module ID中特定的部分,进行转义,如以上代码中对app这个部分,转义为../app,这表示一个相对路径,相对位置是baseUrl所指定的目录,由项目结构可知,../app其实对应的是scirpt/app目录。正由于有这个转义的存在,因此以上代码中的app/bar才能被正确解析,不然还按baseUrl + moduleID的规则,app/bar不是应该被解析成scripts/lib/app/bar.js吗,但实际并不是如此,app/bar被解析成scripts/app/bar.js,其中起关键做用的就是paths的配置。经过这个举例,能够看出module ID并不必定是js文件路径中的一部分,paths的配置对于路径过程的js特别有效,由于能够简化它的module ID。

另外第一个模块的ID为foo,同时没有paths的转义,因此根据解析规则,它的文件路径时:scripts/lib/foo.js。

paths的配置中只有当模块位于baseUrl所指定的文件夹的同层目录,或者更上层的目录时,才会用到../这种相对路径。

例3,项目结果同例1,main.js同例2:

这里要说明的问题稍微特殊,不以main.js为例,而以app.js为例,且app依赖bar,固然config仍是须要在main.js中定义的,因为这个问题在定义模块的时候更加常见,因此用define来举例,假设app.js模块以下定义:

?
1
2
3
4
5
6
7
define([ './bar' ], function (bar) {
    return {
      doSth: function () {
        bar.doSth();
      }
    }
});

上面的代码经过define定义了一个模块,这个define函数后面介绍如何定义模块的时候再来介绍,这里简单了解。这里这种用法的第一个参数跟require函数同样,是一个依赖数组,第二个参数是一个回调,也是在全部依赖加载成功以后调用,这个回调的返回值会成为这个模块的引用被其它模块所使用。

这里要说的问题仍是跟解析规则相关的,若是彻底遵照RJ的解析规则,这里的依赖应该配置成app/bar才是正确的,但因为app.js与bar.js位于同一个目录,因此彻底可利用./这个同目录的相对标识符来解析js,这样的话只要app.js已经加载成功了,那么去同目录下找bar.js就确定能找到了。这种配置在定义模块的时候很是有意义,这样你的模块就不依赖于放置这些模块的文件夹名称了。

4.06 RJ的异步加载
RJ无论是require方法仍是define方法的依赖模块都是异步加载的,因此下面的代码不必定能解析到正确的JS文件:

?
1
2
3
4
5
6
7
8
9
10
11
12
<script data-main= "scripts/main" src= "scripts/require.js" ></script>
<script src= "scripts/other.js" ></script>
//main.js
require.config({
   paths: {
     foo: 'libs/foo-1.1.3'
   }
});
//other.js
require( [ 'foo' ], function ( foo ) {
   //foo is undefined
});

因为main.js是异步加载的,因此other.js会比它先加载,可是RJ的配置存在于main.js里面,因此在加载other.js读不到RJ的配置,在other.js执行的时候解析出来的foo的路径就会变成scripts/foo.js,而正确路径应该是scripts/libs/foo-1.1.3.js。

尽管RJ的依赖是异步加载的,可是已加载的模块在屡次依赖的时候,不会再从新加载:

?
1
2
3
4
5
define([ 'require' , 'app/bar' , 'app/app' ], function (require) {
   var bar= require( "app/bar" );
   var app= require( "app/app" );
   //use bar and app do sth
});

上面的代码,在callback定义的时候,只用了一个形参,这主要是为了减小形参的数量,避免整个回调的签名很长。依赖的模块在回调内部能够直接用require(moduleID)的参数获得,因为在回调执行前,依赖的模块已经加载,因此此处调用不会再从新加载。可是若是此处获取一个并不在依赖数组中出现的module ID,require颇有可能获取不到该模块引用,由于它可能须要从新加载,若是它没有在其它模块中被加载过的话。

4.07 RJ官方推荐的JS文件组织结构
RJ建议,文件组织尽可能扁平,不要多层嵌套,最理想的是跟项目相关的放在一个文件夹,第三方库放在一个文件夹,以下所示:

4.08 使用define定义模块
AMD规定的模块定义规范为:

?
1
2
3
4
5
6
define(id?, dependencies?, factory);
 
其中:
id: 模块标识,能够省略。
dependencies: 所依赖的模块,能够省略。
factory: 模块的实现,或者一个JavaScript对象

关于第一个参数,本文不会涉及,由于RJ建议全部模块都不要使用第一个参数,若是使用第一个参数定义的模块成为命名模块,不适用第一个参数的模块成为匿名模块,命名模块若是改名,全部依赖它的模块都得修改!第二个参数是依赖数组,跟require同样,若是没有这个参数,那么定义的就是一个无依赖的模块;最后一个参数是回调或者是一个简单对象,在模块加载完毕后调用,固然没有第二个参数,最后一个参数也会调用。

本部分所举例都采用以下项目结构:

1. 定义简单对象模块:

app/bar.js

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
define({
  bar: 'I am bar.'
});
利用main.js测试:
require.config({
   baseUrl: 'scripts/lib' ,
   paths: {
     app: '../app'
   }
});
 
require([ 'app/bar' ], function (bar) {
   console.log(bar); // {bar: 'I am bar.'}
});

2. 定义无依赖的模块:

app/nodec.js:

?
1
2
3
4
5
define( function () {
   return {
     nodec: "yes, I don't need dependence."
   }
});

利用main.js测试:

?
1
2
3
4
5
6
7
8
9
10
require.config({
   baseUrl: 'scripts/lib' ,
   paths: {
     app: '../app'
   }
});
 
require([ 'app/nodec' ], function (nodec) {
   console.log(nodec); // {nodec: yes, I don't need dependence.'}
});

3. 定义依赖其它模块的模块:

app/dec.js:

?
1
2
3
4
5
6
define([ 'jquery' ], function ($){
   //use $ do sth ...
   return {
     useJq: true
   }
});

利用main.js测试:

?
1
2
3
4
5
6
7
8
9
10
require.config({
   baseUrl: 'scripts/lib' ,
   paths: {
     app: '../app'
   }
});
 
require([ 'app/dec' ], function (dec) {
   console.log(dec); //{useJq: true}
});

4. 循环依赖:
当一个模块foo的依赖数组中存在bar,bar模块的依赖数组中存在foo,就会造成循环依赖,稍微修改下bar.js和foo.js以下。

app/bar.js:

?
1
2
3
4
5
6
7
8
define([ 'foo' ], function (foo){
  return {
  name: 'bar' ,
  hi: function (){
   console.log( 'Hi! ' + foo.name);
  }
  }
});

lib/foo.js:

?
1
2
3
4
5
6
7
8
define([ 'app/bar' ], function (bar){
  return {
  name: 'foo' ,
  hi: function (){
   console.log( 'Hi! ' + bar.name);
  }
  }
});

利用main.js测试:

?
1
2
3
4
5
6
7
8
9
10
11
12
require.config({
   baseUrl: 'scripts/lib' ,
   paths: {
     app: '../app'
   }
});
 
 
require([ 'app/bar' , 'foo' ], function (bar, foo) {
   bar.hi();
   foo.hi();
});

运行结果:

若是改变main.js中require部分的依赖顺序,结果:

循环依赖致使两个依赖的module之间,始终会有一个在获取另外一个的时候,获得undefined。解决方法是,在定义module的时候,若是用到循环依赖的时候,在define内部经过require从新获取。main.js不变,bar.js改为:

?
1
2
3
4
5
6
7
8
9
define([ 'require' , 'foo' ], function (require, foo) {
   return {
     name: 'bar' ,
     hi: function () {
      foo = require( 'foo' );
       console.log( 'Hi! ' + foo.name);
     }
   }
});

foo.js改为:

?
1
2
3
4
5
6
7
8
9
define([ 'require' , 'app/bar' ], function (require, bar) {
   return {
     name: 'foo' ,
     hi: function () {
      bar = require( 'app/bar' );
       console.log( 'Hi! ' + bar.name);
     }
   }
});

利用上述代码,从新执行,结果是:

模块定义总结:无论模块是用回调函数定义仍是简单对象定义,这个模块输出的是一个引用,因此这个引用必须是有效的,你的回调不能返回undefined,可是不局限于对象类型,还能够是数组,函数,甚至是基本类型,只不过若是返回对象,你能经过这个对象组织更多的接口。

4.09 内置的RJ模块
再看看这个代码:

?
1
2
3
4
5
6
7
8
9
define([ 'require' , 'app/bar' ], function (require) {
   return {
     name: 'foo' ,
     hi: function () {
       var bar = require( 'app/bar' );
       console.log( 'Hi! ' + bar.name);
     }
   }
});

依赖数组中的require这个moduleID对应的是一个内置模块,利用它加载模块,怎么用你已经看到了,好比在main.js中,在define中。另一个内置模块是module,这个模块跟RJ的另一个配置有关,具体用法请在第5大部分去了解。

4.10 其它RJ有用功能
1. 生成相对于模块的URL地址

?
1
2
3
define([ "require" ], function (require) {
   var cssUrl = require.toUrl( "./style.css" );
});

这个功能在你想要动态地加载一些文件的时候有用,注意要使用相对路径。

2. 控制台调试

require("module/name").callSomeFunction()
假如你想在控制台中查看某个模块都有哪些方法能够调用,若是这个模块已经在页面加载的时候经过依赖被加载事后,那么就能够用以上代码在控制台中作各类测试了。

5. RequireJS经常使用配置总结
在RJ的配置中,前面已经接触到了baseUrl,paths,另外几个经常使用的配置是:

  • shim
  • config
  • enforceDefine
  • urlArgs

5.01 shim
为那些没有使用define()来声明依赖关系、设置模块的"浏览器全局变量注入"型脚本作依赖和导出配置。

例1:利用exports将模块的全局变量引用与RequireJS关联

main.js以下:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
require.config({
   baseUrl: 'scripts/lib' ,
   paths: {
     app: '../app'
   },
   shim: {
    underscore: {
    exports: '_'
    }
   }
});
 
require([ 'underscore' ], function (_) {
   // 如今能够经过_调用underscore的api了
});

如你所见,RJ在shim中添加了一个对underscore这个模块的配置,并经过exports属性指定该模块暴露的全局变量,以便RJ可以对这些模块统一管理。

例2:利用deps配置js模块的依赖

main.js以下:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
require.config({
   baseUrl: 'scripts/lib' ,
   paths: {
     app: '../app'
   },
   shim: {
    backbone: {
     deps: [ 'underscore' , 'jquery' ],
     exports: 'Backbone'
    }
   }
});
 
require([ 'backbone' ], function (Backbone) {
   //use Backbone's API
});

因为backbone这个组件依赖jquery和underscore,因此能够经过deps属性配置它的依赖,这样backbone将会在另外两个模块加载完毕以后才会加载。

例3:jquery等库插件配置方法

代码举例以下:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
requirejs.config({
   shim: {
     'jquery.colorize' : {
       deps: [ 'jquery' ],
       exports: 'jQuery.fn.colorize'
     },
     'jquery.scroll' : {
       deps: [ 'jquery' ],
       exports: 'jQuery.fn.scroll'
     },
     'backbone.layoutmanager' : {
       deps: [ 'backbone' ]
       exports: 'Backbone.LayoutManager'
     }
   }
});

5.02 config
经常须要将配置信息传给一个模块。这些配置每每是application级别的信息,须要一个手段将它们向下传递给模块。在RequireJS中,基于requirejs.config()的config配置项来实现。要获取这些信息的模块能够加载特殊的依赖“module”,并调用module.config()。

例1:在requirejs.config()中定义config,以供其它模块使用

?
1
2
3
4
5
6
7
8
9
10
requirejs.config({
   config: {
     'bar' : {
       size: 'large'
     },
     'baz' : {
       color: 'blue'
     }
   }
});

如你所见,config属性中的bar这一节是在用于module ID为bar这个模块的,baz这一节是用于module ID为baz这个模块的。具体使用以bar.js举例:

?
1
2
3
define([ 'module' ], function (module) {
   //Will be the value 'large'var size = module.config().size;
});

前面提到过,RJ的内置模块除了require还有一个module,用法就在此处,经过它能够来加载config的内容。

5.03 enforceDefine
若是设置为true,则当一个脚本不是经过define()定义且不具有可供检查的shim导出字串值时,就会抛出错误。这个属性能够强制要求全部RJ依赖或加载的模块都要经过define或者shim被RJ来管理,同时它还有一个好处就是用于错误检测。

5.04 urlArgs
RequireJS获取资源时附加在URL后面的额外的query参数。做为浏览器或服务器未正确配置时的“cache bust”手段颇有用。使用cache bust配置的一个示例:

urlArgs: "bust=" + (new Date()).getTime()
6. 错误处理
6.01 加载错误的捕获
IE中捕获加载错误不完美:

IE 6-8中的script.onerror无效。没有办法判断是否加载一个脚本会致使404错;更甚地,在404中依然会触发state为complete的onreadystatechange事件。
IE 9+中script.onerror有效,但有一个bug:在执行脚本以后它并不触发script.onload事件句柄。所以它没法支持匿名AMD模块的标准方法。因此script.onreadystatechange事件仍被使用。可是,state为complete的onreadystatechange事件会在script.onerror函数触发以前触发。
因此为了支持在IE中捕获加载错误,须要配置enforceDefine为true,这不得不要求你全部的模块都用define定义,或者用shim配置RJ对它的引用。

注意:若是你设置了enforceDefine: true,并且你使用data-main=""来加载你的主JS模块,则该主JS模块必须调用define()而不是require()来加载其所需的代码。主JS模块仍然可调用require/requirejs来设置config值,但对于模块加载必须使用define()。好比原来的这段就会报错:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
require.config({
  enforceDefine: true ,
   baseUrl: 'scripts/lib' ,
   paths: {
     app: '../app'
   },
   shim: {
    backbone: {
    deps: [ 'underscore' , 'jquery' ],
       exports: 'Backbone'
    }
   }
});
require([ 'backbone' ], function (Backbone) {
   console.log(Backbone);
});

把最后三行改为:

?
1
2
3
define([ 'backbone' ], function (Backbone) {
   console.log(Backbone);
});

才不会报错。

6.02 paths备错

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
requirejs.config({
   //To get timely, correct error triggers in IE, force a define/shim exports check.
   enforceDefine: true ,
   paths: {
     jquery: [
       //If the CDN location fails, load from this location
       'lib/jquery'
     ]
   }
});
 
//Later
require([ 'jquery' ], function ($) {
});

上述代码先尝试加载CDN版本,若是出错,则退回到本地的lib/jquery.js。

注意: paths备错仅在模块ID精确匹配时工做。这不一样于常规的paths配置,常规配置可匹配模块ID的任意前缀部分。备错主要用于很是的错误恢复,而不是常规的path查找解析,由于那在浏览器中是低效的。

6.03 全局 requirejs.onError
为了捕获在局域的errback中未捕获的异常,你能够重载requirejs.onError():

?
1
2
3
4
5
6
7
8
requirejs.onError = function (err) {
   console.log(err.requireType);
   if (err.requireType === 'timeout' ) {
     console.log( 'modules: ' + err.requireModules);
   }
 
   throw err;
};
相关文章
相关标签/搜索