以前一直有据说RequireJS,可是一直都没机会去了解,只知道它是一个给js作模块化的API。最近在作React,其组件化的思想和js模块化的思想不谋而合。就想在项目中应用React的同时,也把RequireJS加进来,看看会不会对页面加载或者开发有很好的效果。javascript
在说明什么是RequireJS以前,不得不提的就是Javascript模块化历史的背景。其实在早期,javascript做为一门新兴的脚本语言出现,有着庞大的愿景,它并非做为一门仅仅针对客户端设计的语言。只是说后来web应用的流行,javascript做为浏览器端脚本语言而迅速传开,加上Netscape和微软的竞争将其过早的标准化。因此就致使了JS的诸多缺陷,其中一个就是模块化(可是你能够惊奇地发现其实javascript有将import,export等做为保留字,说明设计的时候实际上是有考虑的,新的标准es6也让原生支持模块化了)。而后随着web应用愈来愈复杂,嵌入的javascript代码愈来愈多,还有node的兴起,模块化编程就变成了必须。html
因此就有了后来Dojo工具包和Google的Closure库支持的模块系统。还有两个很是通用的标准规范,CommonJS和AMD。这里就不展开说了,咱们只须要知道,实现CommonJS规范的API是同步加载模块的,而实现AMD规范的API是则是异步加载模块。
因此理论上来讲,AMD规范的非阻塞加载更加适合浏览器端。而RequireJS就是AMD规范的最好实现。抄一段官方文档对RequireJS的描述:java
RequireJS 是一个JavaScript模块加载器。它很是适合在浏览器中使用, 它很是适合在浏览器中使用,但它也能够用在其余脚本环境, 就像 Rhino and Node. 使用RequireJS加载模块化脚本将提升代码的加载速度和质量。node
因此,知道了RequireJS是干什么的,也差很少知道为何咱们要使用RequireJS了。不过仍是总结一下用RequireJS的好处吧。jquery
<script data-main="js/main" src="xxx/xxxx/require.js"></script>
使用RequireJS,你只须要引入一个require.js便可。须要说明的是,一个比较好的实践,就是你的页面上面应该也只须要经过\<script\>标签引入这一个js便可。而后你这个页面的全部业务逻辑只须要在main.js里面写(data-main属性做用后面会有讲)就能够了。其它引用的依赖怎么办?固然是经过require按需引入啊!git
其实Requirejs整个源文件包括注释就2000来行,其对外暴露的变量其实就三个,requirejs,require,define。es6
这其中requirejs 只是require的一个别名,目的是若是页面中有require其它实现了,你仍是能经过使用requirejs来使用requireJS API的(本文中没有相关冲突,因此仍是使用require)。github
因此这意味着做为入门,你只须要掌握require,require.config,define这三样就能够了。web
本文将以介绍require,require.config,data-main,define的顺序来介绍RequireJS。让比较简单的RequireJS更加简单,争取让你们只看这篇文章就能用好RequireJS。至于RequireJS是如何解决循环依赖,对于没有实现amd的模块如何经过shim来导出,如何在node中使用等问题。本文并无说起,详细有须要能够去官方查阅。npm
首先,先无论三七二十一,咱们先按照下面的方式建立一个这样的目录结构:
而后require.js能够经过npm下载或者在官网得到。jquery同理,jquery须要下载1.7.0或以上的版本。而后把对应的代码拷入对应的文件中,给出余下两个文件的代码:
// js/script/index.html
<!DOCTYPE html> <html> <head> <title>Require Demo 1</title> </head> <body> <div> <h1>Require Demo 1 -- usage of Require()</h1> <button id="contentBtn">Click me</button> <p id="messagebox"></p> </div> <script data-main="js/script/main" src="js/lib/require.js" type="text/javascript"></script> </body> </html>
// js/script/main.js require.config( { paths: { 'jquery': '../lib/jquery-1.7.2' } } ); require(['jquery'],function ($) { $(document).on('click','#contentBtn',function(){ $('#messagebox').html('You have access Jquery by using require()'); }); });
先看index.html的代码,其实比较简单,页面上在js中会用到的就是一个button和一个p标签。而后整个页面就只是一个js文件是经过\<script\>标签加载的,就是require.js。注意到标签中有一个data-main属性,你如今只须要了解require.js会在加载完成之后经过回调方法去加载这个data-main里面的js文件,因此这个js文件被加载的时候,RequireJS已经加载执行完毕。
而后接着看main.js文件,里面被一个匿名当即执行函数所包括。在require.config(...)中,能够配置许多配置项,后面会有详细说明。上面在config中添加了一个path,在path配置了一个模块ID和路径的映射,这样在后续的全部函数中就能够直接经过模块ID来引入依赖,而不用再屡次引入依赖屡次输入路径带来的麻烦。
而后接着就是咱们的require(...)函数了。上面的语法中require函数接受的第一个参数是,所依赖模块的一个数组。即便你只须要传入一个依赖,也须要把这个依赖放进数组中传入。若是你有如本例子中设置了模块ID和路径的映射,那你在传入依赖的时候就可使用模块ID来代替路径,若是没有配置模块ID你固然也能够经过路径来引进对应的模块。接着是传入回调函数,当引入的依赖加载完毕后,这个回调函数就会被触发。若是你传入的依赖有注入变量(函数),而后在回调函数中须要用到,你就须要按照顺序在回调函数的参数中添加别名,在本例子中能够经过别名$来使用jQuery的相关API。因此有注入的模块须要放在无注入或者不须要调用模块的模块前面,方便回调函数传入别名。例子中在回调函数中为id为contentBtn的button注册监听事件,若是触发,则往id为messagebox的p标签添加相应的内容。
另外还须要额外说明的是路径,不论是在配置中写路径仍是直接在require函数中写路径,你都须要了解requireJS在不一样状况下的相对路径。
如下是相对路径的规则:
1.若是\<script\>标签引入require.js时没有指定data-main属性,则以引入该js的html文件所在的路径为根路径。
2.若是有指定data-main属性,也就是有指定入口文件,则以入口文件所在的路径为根路径。在本例子中也就是main.js所在的script文件夹就是根路径,这也是为何配置jQuery的时候须要返回上层目录再进入lib目录才能找到jQuery文件。
3.若是再require.config()中有配置baseUrl,则以baseUrl的路径为根路径。
以上三条优先级逐级提高,若是有重叠,后面的根路径覆盖前面的根路径。
打开网页,而后你就应该看到这样的页面:
点击按钮,有以下效果,说明经过RequireJS已载入Jquery,而且经过Jquery绑定了监听事件。
讲完了如何引入模块,如今讲如何定义一个模块,require定义一个模块是经过 define = function (name, deps, callback)完成的,第一个参数是定义模块名,第二个参数是传入定义模块所须要的依赖,第三个函数则是定义模块的主函数,主函数和require的回调函数同样,一样是在依赖加载完之后再调用执行。
先看个例子:
// js/script/desc.js define(function(){ return{ decs : 'this js will be request only if it is needed', }; })
// 而后在main.js的添加以下代码 // js/script/main.js $('#messagebox').html('You have access Jquery by using require()'); + require(['script/desc'],function(desc){ + alert(JSON.stringify(desc));
再次打开网页,打开network视图,点击按钮,经过require得到的desc模块就会alert出来,同时你会发现,desc.js是按需请求的,并非在页面一开始的时候就请求的。
// js/script/alertdesc.js define(['script/desc'],function(desc){ return function (){ alert(JSON.stringify(desc)); }; })
// 而后在main.js的再作以下修改 // js/script/main.js $('#messagebox').html('You have access Jquery by using require()'); - require(['script/desc'],function(desc){ - alert(JSON.stringify(desc)); + require(['script/alertdesc'],function(alertdesc){ + alertdesc();
若是你细心,你可能会发现,刚刚define函数,有一个参数name是用来定义模块名的(也就是第一个传参),为何上面两个例子都没有用到。其实我确实能够添加模块名,以下:
// js/script/alertdesc.js define(['script/desc'],function(desc){ ..... }) //Change To define('/script/alertdesc',['script/desc'],function(desc){ ..... })
可是,这样作感受不颇有必要,由于若是哪一天我将这个文件转移到其余目录下,那我就得在这这里再修改一次模块名。官方其实也不推荐,用官方的说法是:让优化工具去自动生成这些模块名吧!
在上面一节介绍require()函数的时候,咱们已经接触过require.config(...)了。其实说白了,在require.config()作的一些修改会影响到全局require的一些特性。如上面的,你设置了baseUrl
,其require的根路径就以这个路径为准,你在path中设置了模块ID与路径的映射,后面须要用到相关模块的时候直接使用模块ID代替路径就行了,设置map能够在不一样路径下用相同的模块ID调用不一样版本的模块。
其实这里并不打算对require.config()的具体配置展开来介绍,有须要能够直接去官网查阅相关配置信息加进来就行了。
始终以为require.config()应该抽出来,单独放在一个js文件里面,这样方便移植和重用。在github上看了些例子,找到一个比较好的放置require.config的地方,放在这里能够参考:
// 添加config.js // js/script/config.js define(function(){ require.config({ baseUrl: './js/', paths: { 'jquery': 'lib/jquery-1.7.2' } }); });
// 替换main.js // js/script/main.js require(['config'],function(){ require(['jquery'],function ($) { $(document).on('click','#contentBtn',function(){ $('#messagebox').html('You have access Jquery by using require()'); require(['script/alertdesc'],function(alertdesc){ alertdesc(); }); }); }); });
还记得刚刚的\<script\>引入RequireJS时标签中有一个data-main属性么?当require.js加载的时候会检查data-main属性,因此你能够在data-main指向的脚本(也就是本例子中的js/main.js)中设置模块加载的选项,而后在这个脚本加载第一个应用模块。注意,你在main.js中所设置的脚本是异步加载并经过回调来执行的,这意味着若是你在页面中有经过\<script\>引入其它的脚本,那不能保证在main.js里面作的配置会在其它脚本中生效。
例如:
<script data-main="scripts/main" src="scripts/require.js"></script> <script src="scripts/other.js"></script>
// scripts/main.js: require.config({ paths: { foo: 'libs/foo-1.1.3' } });
// scripts/other.js:
// 因为main.js会是在require.js异步加载完之后再经过回调去执行main.js的 // 因此other.js里面执行的这个require函数可能会发生在main.js的require.config执行以前 // 所以require.config会去尝试去加载"scripts/foo.js",而不是"scripts/libs/foo-1.1.3.js" require( ['foo'], function( foo ) { });
这个例子是官方的。从这里也能够看出来,为何如前文所说的。页面中最好只有一个入口点文件(属性data-main中引入的main.js),而后这个入口点文件里引入或者编写配置,加载相关应用模块。
固然你也能够像官方给的第二种方案,不设置入口点,而后在每一个require回调中再引入相关配置,不过那样很麻烦并且不易于维护。这里就不给出例子了,有须要能够去官网看。
以上就是关于关于RequireJS简单使用的介绍了,你们有须要能够直接看源码,大概就2000多行,不看具体实现,看它对几个函数声明的描述,对使用起来也是颇有帮助的,你会发现有一些连官方文档都没说起到的一些特性(好比require()方法能够直接传入config配置做为第一个参数)。
另外,说一点小插曲,若是须要查阅RequireJS官方的API,有条件的仍是建议直接访问英文官方文档。若是说中文的官方文档说还停留在老版本,翻译得比较生涩难懂就算了。一些很明显有错误的描述就真的是责任问题了。我在看中文文档的时候真是各类难移理解,后来直接看英文文档,则顺畅不少。很少说,贴张图让你们感觉一下英文文档和中文文档对于waitSeconds的描述: