用requireJS进行模块化的网站开发
2013-12-24 16:51 king0222 阅读(715) 评论(0) 编辑 收藏 举报用requireJS进行模块化的网站开发
一.为什么选择requireJS
现目前网上流行两种能进行模块化管理的模块加载器,一种便是遵循AMD(异步模块定义)规范的requireJS,另一种便是遵循CMD(通用模块定义)规范的seaJS.
至于什么是AMD规范什么是CMD规范,简单点说就是一种代码的书写格式或者是API的调用形式。具体看:
两个加载器的模块定义方式略有不同, CMD 推崇依赖就近,AMD 推崇依赖前置。看代码:
1 // CMD 2 define(function(require, exports, module) { 3 var a = require('./a') 4 a.doSomething() 5 // 此处略去 100 行 6 var b = require('./b') // 依赖可以就近书写 7 b.doSomething() 8 // ... 9 }) 10 11 // AMD 默认推荐的是 12 define(['./a', './b'], function(a, b) { // 依赖必须一开始就写好 13 a.doSomething() 14 // 此处略去 100 行 15 b.doSomething() 16 ... 17 })
在这里选择使用遵循AMD规范的requireJS的一个最重要原因是requireJS有完善的API文档,另外一个就是自己喜好选择了。
注意:本文档不会详细的介绍AMD规范外的细节,需要了解详细的可以看官方文档。
二.requireJS 如何使用
在开始页面中引入requireJS,像如下:
1 <!--下面的data-main属性将会配置一个baseUrl(后面解释)的路径为scripts目录下--> 2 <script data-main="scripts/main.js" src="scripts/require.js"></script>
在requireJS中最好根据baseUrl和path参数来配置模块,而尽量避免使用相对路径的形式,这会给后期的项目优化带来方便。(现在不清楚,后面的解释)
在创建的项目中,目录结构可能像下面这样:
在index.html页面中,引入的脚本为:
<script data-main="js/app.js" src="js/require.js"></script>
在app.js中代码如下:
1 requirejs.config({ 2 //baseUrl路径配置加载默认的模块,像jquery之类的库 3 baseUrl: 'js/lib', 4 //如果有另外的一些自定义的模块在另一个文件路径下,可以配置paths,配置值后面无需加上后缀.js,加载器会默认将模块认为是js文件,会自动补全。 5 paths: { 6 app: '../app' 7 } 8 }); 9 10 // 在这里开始整个应用的逻辑,应注意线面的第一行中数组的三个模块名对应着callback中的3个参数,顺序不能搞错 11 requirejs(['jquery', 'canvas', 'app/sub'], 12 function ($, canvas, sub) { 13 //jQuery, canvas and the app/sub module are all 14 //loaded and can be used here now. 15 });
三.定义模块
1.如果模块中不需要依赖其他模块的,而仅仅是一个属性名/值对象字面量,可以像如下方式定义该模块:
1 define({ 2 color: "black", 3 size: "unisize" 4 });
2.如果该模块需要用到一些方法进行逻辑操作,可以在该模块中添加一个funciton来定义,这也是常用的模块定义方法:
1 define(function () { 2 //在这里TODO... 3 4 return { 5 color: "black", 6 size: "unisize" 7 } 8 });
3.第三种也就是我们可能最常用的模块定义方式,需要依赖一些模块,并会依据这些模块来进行一些操作:
1 //文件存放于my/shirt.js 2 define(["./cart", "./inventory"], function(cart, inventory) { 3 //return an object to define the "my/shirt" module. 4 return { 5 color: "blue", 6 size: "large", 7 addToCart: function() { 8 inventory.decrement(this); 9 cart.add(this); 10 } 11 } 12 });
前面提到过,依赖项跟紧跟的函数中的参数一一对应,根据上面代码的依赖关系可以看出目录结构应该是(都位于同一级目录下):
- my/cart.js
- my/inventory.js
- my/shirt.js
4.如果我们需要定义一个没有返回值的模块,而仅仅作为一个函数使用,可以像下面这样返回一个函数:
1 define(["my/cart", "my/inventory"], 2 function(cart, inventory) { 3 //return a function to define "foo/title". 4 //It gets or sets the window title. 5 return function(title) { 6 return title ? (window.title = title) : 7 inventory.storeName + ' ' + cart.name; 8 } 9 } 10 );
5.模块的定义还可以给它加个模块名,仅仅需要在依赖项数组的前面加上一个字符串而已,像如下:
1 define("foo/title", 2 ["my/cart", "my/inventory"], 3 function(cart, inventory) { 4 //Define foo/title object in here. 5 } 6 );
不过不推荐这样,这对代码移植来说是绝对是个累赘,官方也不推荐写上模块名。
模块的一些其他说明:
1.如果想生成模块的关联urls,可以使用require.toUrl()方法,前提是依赖require:
1 define(["require"], function(require) { 2 var cssUrl = require.toUrl("./style.css"); 3 });
2.在项目开发中,常常需要控制台debug,可以想下面这样来调用对应的模块方法:
1 require("module/name").callSomeFunction()
3.循环依赖,如果有两个模块a和b,a需要依赖b,b需要依赖a,以模块b的定义为例:
1 define(["require", "a"], 2 function(require, a) { 3 //"a" in this case will be null if a also asked for b, 4 //a circular dependency. 5 return function(title) { 6 return require("a").doSomething(); 7 } 8 } 9 );
注意到第六行,这是实现循环依赖的关键,就是讲require加为依赖项,看懂就行了。
4.JSONP依赖,如果服务器端实现了JSONP提供数据,那么我们在定义模块的时候需要像下面这样去写:
1 require(["http://example.com/api/data.json?callback=define"], 2 function (data) { 3 //The data object will be the API response for the 4 //JSONP data call. 5 console.log(data); 6 } 7 );
第一行中,依赖项是一条url,并且在url后面手动加上"?callback=define",不包括引号,在后面的函数中便能够在参数中拿到JSONP返回的JSON格式的数据了。
4.有时候我们可能需要取消模块的定义,利用require.undef()就可以完成这样的事情。
小结:模块定义无非就是将功能的实现单独拆分出来,类似于在一个函数中return一个对象或者其他的东西,便于其他模块获取模块方法或信息。(模块定义没想象中的那么难,理解其基本原理即可)
四.配置信息
配置信息应该写在顶层html页面中,或者顶层的javascript文件中(非模块定义页)。像下面的index.html页面中配置信息。
1 <script src="scripts/require.js"></script> 2 <script> 3 require.config({ 4 baseUrl: "/another/path", 5 paths: { 6 "some": "some/v1.0" 7 }, 8 waitSeconds: 15 9 }); 10 require( ["some/module", "my/module", "a.js", "b.js"], 11 function(someModule, myModule) { 12 //This function will be called when all the dependencies 13 //listed above are loaded. Note that this function could 14 //be called before the page is loaded. 15 //This callback is optional. 16 } 17 ); 18 </script>
下面我们就来逐个讲解配置的信息:
1.baseUrl:默认加载的模块路径,一般是脚本库路径,像jQuery.
2.paths:这里可以定义一些不同于baseUrl路径的代码目录别名,但路径是相对于baseUrl的,这点注意。
3.waitSeconds:这里设置脚本加载时长,如果超过该时长将放弃加载该脚本。
4.shim:该参数配置那些非模块化的代码文件。例如jQuery,它的代码格式不是一个requireJS所定义的模块模式,因此,我们需要用shim参数来将jQuery配置成一个可用的模块。看下面的代码:
1 requirejs.config({ 2 shim: { 3 'backbone': { 4 deps: ['underscore', 'jquery'], 5 exports: 'Backbone' 6 }, 7 'underscore': { 8 exports: '_' 9 }, 10 'foo': { 11 deps: ['bar'], 12 exports: 'Foo', 13 init: function (bar) { 14 //类似于初始化,参数要传入依赖项 15 return this.Foo.noConflict(); 16 } 17 } 18 } 19 }); 20 21 //然后在另一个文件名为MyModel.js的文件中创建模块,依赖项为shim中配置的backbone,这样就可以使用Backbone来编写自己的代码了 22 define(['backbone'], function (Backbone) { 23 return Backbone.Model.extend({}); 24 });
5.map:该参数在项目中需要用到多版本脚本库的时候会非常有用,比方说一个模块需要依赖低版本的foo脚本库,而另一个模块需要依赖高版本的foo脚本库的时候,这时可以用map参数来正确匹配其依赖项。像下面的代码所示:
1 requirejs.config({ 2 map: { 3 'some/newmodule': { 4 'foo': 'foo1.2' 5 }, 6 'some/oldmodule': { 7 'foo': 'foo1.0' 8 } 9 } 10 });
上面的代码中,some/newmodule模块需要依赖foo库的高版本,而some/oldmodule需要依赖其低版本, 目录结构应该是这样的:
- foo1.0.js
- foo1.2.js
- some/
- newmodule.js
- oldmodule.js
或者可以使用通配符*表示所有的模块,像下面代码所示:
1 requirejs.config({ 2 map: { 3 '*': { 4 'foo': 'foo1.2' 5 }, 6 'some/oldmodule': { 7 'foo': 'foo1.0' 8 } 9 } 10 });
通配符匹配了所有的模块依赖高版本的foo库,除了后面紧跟着的some/oldmodule模块使用低版本库外。
6.config:该参数用来配置模块信息,如下代码所示:
1 requirejs.config({ 2 config: { 3 'bar': { 4 size: 'large' 5 }, 6 'baz': { 7 color: 'blue' 8 } 9 } 10 }); 11 12 //这里是commonJS规范的写法,不详述 13 define(function (require, exports, module) { 14 //size值得到'large' 15 var size = module.config().size; 16 }); 17 18 define(['module'], function (module) { 19 //color值得到'blue' 20 var color = module.config().color; 21 });
7.packages:这个单词翻译成中文叫包,有后台语言编程基础的能理解其含义,在这里配置packages就是将整个包(包含一个或多个模块的文件)配置一个别名,默认的包里面最好有一个main.js的模块。像下面这样的目录结构:
- project-directory/
- project.html
- script/
- cart/
- main.js
- store/
- main.js
- util.js
- main.js
- require.js
- cart/
首先我们在project.html中包含require.js
1 <script data-main="scripts/main" src="scripts/require.js"></script>
接着我们可以像这样去加载包:
1 require.config({ 2 "packages": ["cart", "store"] 3 }); 4 5 require(["cart", "store", "store/util"], 6 function (cart, store, util) { 7 //use the modules as usual. 8 });
这里得稍作解释:require(["cart", "store", "store/util"]这行代码中"cart","store"表示了包名,requireJS会默认从包中寻找main.js去加载,因此"cart"=="cart/main.js",同理"store". 因为模块中还需要store包中的util模块,因此需要用包名/路径到模块的方式的去找到所要依赖的模块。
或者你不想默认去加载main.js这个模块,你把main.js名字改为了store.js,那么你可以像下面这样配置:
1 require.config({ 2 packages: [ 3 "cart", 4 { 5 name: "store", 6 main: "store" 7 } 8 ] 9 });
8.context:不描述,因为我也不清楚它是什么意思,看英文原文:
context: A name to give to a loading context. This allows require.js to load multiple versions of modules in a page, as long as each top-level require call specifies a unique context string.
9.deps:依赖性,不多复述
10.callback:这个还是有点用的,在所有依赖项加载完成后,可以在callback中做些啥子测试的都Ok.
11.enforceDefine:强制定义,这个东西设置为true的时候会在非define()定义模块的时候抛出一些异常,不用过度理会这个配置(我是这么觉得)。
12.xhtml:不理解,所以不解释,应该用的不多。
13.urlArgs:这个东东就相当有用了,在开发阶段应该将这个配置加上,等到项目发布的时候再将他去除,切记切记。这里有一个配置例子:
urlArgs: "bust=" + (new Date()).getTime()
scriptType:名字就叫脚本类型,看一下这段英文吧,不懂翻译:
scriptType:Specify the value for the type="" attribute used for script tags inserted into the document by RequireJS. Default is "text/javascript". To use Firefox's JavaScript 1.8 features, use "text/javascript;version=1.8".
五.高级应用
1.如果有加载CDN上的脚本情况,我们可以预留一个本地脚本,以防无法加载CDN上的文件,因此我们可以如下配置:
1 requirejs.config({ 2 //为了能够查看IE中的异常加上enforceDefine 3 enforceDefine: true, 4 paths: { 5 jquery: [ 6 'http://ajax.googleapis.com/ajax/libs/jquery/1.4.4/jquery.min', 7 //如果CDN加载失败,就加载本地 8 'lib/jquery' 9 ] 10 } 11 }); 12 13 require(['jquery'], function ($) { 14 });
2.有时候为了检查错误,我们可以重写requirejs.onError()方法:
1 requirejs.onError = function (err) { 2 console.log(err.requireType); 3 if (err.requireType === 'timeout') { 4 console.log('modules: ' + err.requireModules); 5 } 6 7 throw err; 8 };
3.requirejs本身不支持dom加载完成才去执行脚本,但有一个已经完成的模块为我们提供了该功能,当然你需要下载该模块到本地:
1 require(['domReady'], function (domReady) { 2 domReady(function () { 3 //TODO... 4 }); 5 });
domReady模块能帮我们在dom加载完成后采取执行脚本,但这里有一种跟简便的方式去写这个domReady方法,在依赖项domReady后面紧跟一个!号即可。
1 require(['domReady!'], function (doc) { 2 });
其他更多内容请看requireJS API官方文档