使用RequireJS库加载JavaScript模块的实例教程
js通过script标签的默认加载方式是同步的,即第一个script标签内的js加载完成后,才开始加载第二个,以此类推,直至js文件全部加载完毕。且js的依赖关系必须通过script的顺序才能确保;而在js加载期间,浏览器将停止响应,这大大影响了用户体验,基于此,很多解决js以来和加载的方案出现,requirejs就是其中之一。
requirejs加载的模块,一般为符合AMD标准的模块,即用define定义,用ruturn返回暴露方法、变量的模块;requirejs也可以加载飞AMD标准的模块,但比较麻烦,这次不涉及。
require加载js主涉及以下方面:
- script标签data-main属性声明requirejs加载的入口模块,async="true"(非ie)和defer(ie)标签表明异步加载。
- require.config配置模块对应的路径
- require声明依赖关系
htmldemo
<scriptsrc="js/require.js"deferasync="true"data-main="js/main"> <!--给出requirejs路径,声明为异步加载,指定入口模块为 main.js(可省略.js)-->
main.js
require.config({ //声明模块的位置 paths:{ "jquery":"libs/jquery" "login":"libs/login" } //或使用baseUrl指定所有模块的路径 baseUrl:"js/libs" }); //使用require加载模块,第一个参数为数组,即要加载的模块,将按数组顺序加载;第二个为回调函数,在全部加载完成后执行。 require(['jquery','login'],function($,Login){ alert("jqueryandloginmoduleloadsuccess!"); Login.do_login(); //someelse });
符合amd的loginmodule定义
//依赖jquery的定义 define(['jquery'],function($){ //somedefinations functiondo_login(){ $.post('/sessions/create',{uname:$("#uname").val(), password:$("#password").val()},function(data){ //some }); return{do_login:do_login}; } }); //不依赖其他模块的定义 define(function(){ //somedefinations return{xx:xx}; });
rails没有应用js加载器,一方面是新版本的rails的assetpipe会将所有的js文件打包为一个js文件,没有多个js加载的状态,另一方面turbolink使用褒贬参半的所谓pjax技术,默认链接改为ajax方式,只获取html的bod部分,head部分不变动,使js的加载只在第一次打开网站时进行。
案例一:加载JavaScript文件
<scriptsrc="./js/require.js"></script> <script> require(["./js/a.js","./js/b.js"],function(){ myFunctionA(); myFunctionB(); }); </script>
require方法里的这个字符串数组参数可以允许不同的值,当字符串是以”.js”结尾,或者以”/”开头,或者就是一个URL时,RequireJS会认为用户是在直接加载一个JavaScript文件,否则,当字符串是类似”my/module”的时候,它会认为这是一个模块,并且会以用户配置的baseUrl和paths来加载相应的模块所在的JavaScript文件。配置的部分会在稍后详细介绍。
这里要指出的是,RequireJS默认情况下并没有保证myFunctionA和myFunctionB一定是在页面加载完成以后执行的,在有需要保证页面加载以后执行脚本时,RequireJS提供了一个独立的domReady模块,需要去RequireJS官方网站下载这个模块,它并没有包含在RequireJS中。有了domReady模块,案例一的代码稍做修改加上对domReady的依赖就可以了。
案例二:页面加载后执行JavaScript
<scriptsrc="./js/require.js"></script> <script> require(["domReady!","./js/a.js","./js/b.js"],function(){ myFunctionA(); myFunctionB(); }); </script>
执行案例二的代码后,通过Firebug可以看到RequireJS会在当前的页面上插入为a.js和b.js分别声明了一个<script>标签,用于异步方式下载JavaScript文件。async属性目前绝大部分浏览器已经支持,它表明了这个<script>标签中的js文件不会阻塞其他页面内容的下载。
案例三:RequireJS插入的<script>
<scripttype="text/javascript"charset="utf-8"async=""data-requirecontext="_" data-requiremodule="js/a.js"src="js/a.js"></script>
使用RequireJS来定义JavaScript模块
这里的JavaScript模块与传统的JavaScript代码不一样的地方在于它无须访问全局的变量。模块化的设计使得JavaScript代码在需要访问”全局变量”的时候,都可以通过依赖关系,把这些”全局变量”作为参数传递到模块的实现体里,在实现中就避免了访问或者声明全局的变量或者函数,有效的避免大量而且复杂的命名空间管理。
如同CommonJS的AMD规范所述,定义JavaScript模块是通过define方法来实现的。
下面我们先来看一个简单的例子,这个例子通过定义一个student模块和一个class模块,在主程序中实现创建student对象并将student对象放到class中去。
案例四:student模块,student.js
define(function(){ return{ createStudent:function(name,gender){ return{ name:name, gender:gender }; } }; });
案例五:class模块,class.js
define(function(){ varallStudents=[]; return{ classID:"001", department:"computer", addToClass:function(student){ allStudents.push(student); }, getClassSize:function(){ returnallStudents.length; } }; } );
案例六:主程序
require(["js/student","js/class"],function(student,clz){ clz.addToClass(student.createStudent("Jack","male")); clz.addToClass(student.createStudent("Rose","female")); console.log(clz.getClassSize());//输出2 });
student模块和class模块都是独立的模块,下面我们再定义一个新的模块,这个模块依赖student和class模块,这样主程序部分的逻辑也可以包装进去了。
案例七:依赖student和class模块的manager模块,manager.js
define(["js/student","js/class"],function(student,clz){ return{ addNewStudent:function(name,gender){ clz.addToClass(student.createStudent(name,gender)); }, getMyClassSize:function(){ returnclz.getClassSize(); } }; });
案例八:新的主程序
require(["js/manager"],function(manager){ manager.addNewStudent("Jack","male"); manager.addNewStudent("Rose","female"); console.log(manager.getMyClassSize());//输出2 });
通过上面的代码示例,我们已经清楚的了解了如何写一个模块,这个模块如何被使用,模块间的依赖关系如何定义。还是有一些使用技巧需要提示一下:
尽量不要提供模块的ID,如AMD规范所述,这个ID是可选项,如果提供了,在RequireJS的实现中会影响模块的可迁移性,文件位置变化会导致需要手动修改该ID。
每个JavaScript文件只定义一个模块,模块名称和文件路径的查找算法决定了这种方式是最优的,多个的模块和文件会被优化器进行优化。避免模块的循环依赖,如果实在避免不了,可以模块中加上对”require”模块的依赖,在代码中直接用
require(”dependencyModuleName”)
配置RequireJS:
前面的介绍中,我们似乎忽略了一个基本问题,模块名字是怎么来的?当我在require一个模块时,这个模块是如何映射到具体的JavaScript文件上去?这就涉及到如何配置RequireJS。
最简化的加载RequireJS的方式如案例2所示,在这种情况下,我们没有指定一个baseUrl和paths给RequireJS,如果通过如案例10所示方式,则data-main指定了一个在当前index.html目录并行的文件夹下的/js/main.js作为程序入口,而/js目录也将作为baseUrl在其他模块定义时候使用。
案例九:载入require.js
<scriptdata-main="js/main"src="scripts/require.js"></script>
因此,我们前面示例中的所有模块依赖,都可以去掉”js/”,直接写”student”,”class”,”manager”等。一种更为直接的方式显示指定baseUrl和paths就是利用require.config来设置这些参数。如案例十所示。
案例十.配置RequireJS
<scripttype="text/javascript"src="./js/require.js"></script> <scripttype="text/javascript"> require.config({ baseUrl:"./js", paths:{ "some":"some/v1" }, waitSeconds:10 }); require(["some/module","my/module","./js/a.js"], function(someModule,myModule){} ); </script>
baseUrl指明的是所有模块的baseURL,比如”my/module”所加载的script实际上就是/js/my/module.js。注意,以.js结尾的文件加载时不会使用该baseUrl,它们仍然会使用当前index.html所在的相对路径来加载,所以仍然要加上”./js/”。如果baseUrl没有指定,那么就会使用data-main中指定的路径。
paths中定义的路径是用于替换模块中的路径,如上例中的some/module具体的JavaScript文件路径是/js/some/v1/module.js。waitSeconds是指定最多花多长等待时间来加载一个JavaScript文件,用户不指定的情况下默认为7秒。
另外一个重要的配置是packages,它可以指定其他符合CommonJSAMD规范的目录结构,由此带来了丰富的扩展性。如Dojo、jQuery等的模块也可以通过该配置来让RequireJS加载。
其他可配置的选项还包括locale、context、deps、callback等,有兴趣的读者可以在RequireJS的官方网站查阅相关文档。