require.js学习笔记。
加载JavaScript文件
RequireJS的目的是鼓励代码的模块化,它使用了不同于传统script标签的脚本加载步骤。可以用它来加速、优化代码,但其主要目的还是为了代码的模块化。它鼓励在使用脚本时以module ID替代URL地址。
RequireJs以一个相对于baseUrl的地址来加载所有的代码。页面顶层script标签含有一个特殊属性data-main,require.js使用它来启动脚本加载过程,而baseUrl一般设置到与该属性相一致的目录。
<script data-main="scripts/main.js" src="scripts/requires.js"></script>
baseUrl亦可通过RequireJS config手动设置。如果没有显式指定config及data-main,则默认的baseUrl为包含RequireJS的那个HTML页面的所属目录。
<script data-main="js/app.js" src="js/require.js"></script> //app.js require.config({ baseUrl:'js/lib', paths:{ app:'../app' } }); require(['jquery','canvas','app/sub'],function($,canvas,sub){ //.... })
注意在示例中,三方库如Jquery没有将版本号包含在它们的文件名中。我们建议将版本信息放置在单独的文件中进行跟踪。
理想状况下,没个加载的脚本都是通过define()来定义的一个模块;但是有些浏览器全局变量注入型的传统/遗留库并没有使用define()来定义它们的依赖关系,你必须为此使用shim config来指明它们的依赖关系。如果你没有指明依赖关系,加载可能报错。这是因为基于速度的原因,srequireJS会异步地以无序的形式加载这些库。
data-main入口点
require.js在加载的时候会检查data-main属性:
<script data-main="scripts/main" src="scripts/requires.js"></script>
你可以在data-main指向的脚本中设置模板加载选项,然后加载第一个应用模块。注意:你再main.js中所这是的脚本是异步加载的。所以如果你在页面中配置了其它JS加载,则不能保证它们所依赖的JS已经加载成功。
<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){})
定义模块
模块不同于传统的脚本文件,它良好的定义了一个作用域来避免全局名称空间污染。它可以显式的列出其依赖关系,并以函数参数的形式将这些依赖进行注入,而无需引用全局变量。requireJS的模块是模块模式的一个扩展,其好处是无需全局引用其它模块。
RequireJs模块语法允许它尽快地加载多个模块,虽然加载的顺序不定,但依赖的顺序最终是正确的。同时因为无需创建全局变量,甚至可以做到在同一页面上同时加载同一模块的不同版本。
如果一个模块仅含值对,没有任何依赖,则在define()中定义这些值就好了:
define({ color:'black', size:'unisize' })
如果一个模块没有任何依赖,但需要一个setup工作的函数,则在define()中定义该函数,并将其传给define():
define(function(){ return{ color:'black', size:'unisize' } })
如果模块存在依赖:则第一个参数是依赖的名称数组;第二个参数是函数,在模块的所有依赖加载完毕后,该函数会被调用来定义该模块,因此该模块应该返回一个定义了本模块的object。依赖关系会以参数的形式注入到该函数上,参数列表与依赖名称列表一一对应。
define(['./cart','./inventory'],function(cart,inventory){ return{ color:'blue', size:'large', addToCart:function(){ inventory.decrement(this); cart.add(this); } } })
对模块的返回值类型并没有强制为一定是个object,任何函数的返回值都是允许的。此处是一个返回了函数的模块定义:
define(['my/cat','my/inventory'],{ function(cart,inventory){ return function(title){ return title?(window.title = title):inventory.storeName+''+cart.name; } } })
define()中的相对模块名:为了可以在define()内部使用诸如require('./relative/name')的调用以正确解析相对名称,记得将require本身作为一个依赖注入到模块中:
define(['require','./relative/name'],funciton(require){ var mod = require('./relative/name') })
或者更好地,使用下述转换为CommonJS模块所设的更短的语法:
define(function(require){ var mod = require('./relative/name') })
相对路径在一些场景下格外有用,例如:为了以便于将代码共享给其他人或项目,你在某个目录下创建一些模块。你可以访问模块的相邻模块,无需知道该目录的名称。
生成相对于模块的URL地址:你可能需要生成一个相对于模块的URL地址。你可以将require作为一个依赖注入进来,然后调用require.toUrl()以生成URL:
define(['require'],function(require){ var cssUrl = require.toUrl('./style.css') })
如果你定义了一个循环依赖(a依赖b,b同时依赖a),则在这种情形下当b的模块函数被调用的时候,它会得到一个undefined的a。b可以在模块已经定义好后用require()方法再获取(记得将require作为依赖注入进来):
//b.js define(['require','a'],function(require,a){ return function(title){ return require('a').doSomething(); } })
一般说你无需使用require()去获取一个模块,而是应当使用注入到模块函数参数中的依赖。循环依赖比较罕见,它也是一个重构代码重新设计的i警示灯。
如果你熟悉CommonJS,你可以考虑使用exports为模块建立一个空object,该object可以立即被其它模块引用。再循环依赖的两头都如此操作之后,你就可以完全持有其它模块了。这种方法仅在每个模块都是输出object作为模块值的时候有效,换成函数无效。
//b.js define(function(require,exports,module){ var a = require('a'); exports.foo = function(){ return a.bar(); } })
或则,如果你使用依赖注入数组的步骤,则可用注入特殊exports来解决:
//b.js define(['a','exports'],function(a,exports){ exports.foo = function(){ return a.bar(); } });
JSONP是在javascript中服务调用的一种方式。他仅需简单地通过一个script标签发起HTTP GET请求,是实现跨域服务调用一种公认手段。
为了在RequireJS中使用JSON服务,须要将callback参数的值指定为define。这意味着你可将获取到的JSONP URL的值看成是一个模块定义。
下面是一个调用JSONP API端点的示例。该示例中,JSONP的callback参数为"callback",因此"callback=define"告诉API将JSON响应包裹到一个"define()"中:
require(["http://example.com/api/data.json?callback=define"], function (data) { //The data object will be the API response for the //JSONP data call. console.log(data); } );