requireJs用一种新的script加载方法,这种方法和传统<script>标签是彻底不一样的。它能够运行地更快,而且进行更好地优化,它的主要目的就是为了支持(encourage)模块化(modular)代码的加载。做为其中的一部分,它支持利用模块ID来加载script,而不是script标签里的url属性。
javascript
requireJs加载的全部代码地址都是相对于baseUrl的。页面顶层script标签有一个特殊的属性data-main,require.js用它来启动脚本加载页面,而baseUrl一般设置成这个标签所在的文件夹里。data-main attribute是一个特殊的属性,require.js会用这个属性进行加载。下面这个例子会展现了baseUrl的设置:css
<!--This sets the baseUrl to the "scripts" directory, and loads a script that will have a module ID of 'main'--> <script data-main="scripts/main.js" src="scripts/require.js"></script>
或者,baseUrl能够经过RequireJS config手动(manually)地设置。若是没有明确的(explicit)config设置,或者没有使用data-main属性,那么默认的baseUrl就是包含requireJs的HTML页面所在的目录。html
requireJs默认全部依赖(dependence)资源都死scripts,因此写模块ID时不须要.js的后缀。requireJs翻译模块ID的路径时会自动加上.js尾缀的。运用paths config标签,你能够设置一组scripts脚本的位置。相对于<script>标签,这些能让你用更少的字符来加载script。java
有时候你想直接饮用一个script,而不是依照(conform)“baseUrl+paths"规则来找它。若是一个模块ID由如下之一的规则,这个ID就不会经过”baseUrl+paths"配置来加载script,而是像普通的script url属性来加载。jquery
一般地说,最好经过baseUrl和paths来设置模块ID的路径。这样所,你能够很方便地重命名和重定位脚本(configuring the paths to different locations)。git
类似的,为了不配置凌乱,最好避免多级嵌套(deep folder hierarchies)的方式来加载代码。要么将全部的scripts放在baseUrl的目录中,否则将你的代码分置为目录(library)/第三方目录库(vendor)的结构,能够像如下所示:github
in index.html:web
<script data-main="js/app.js" src="js/require.js"></script>
and in app.js:json
requirejs.config({ //设置默认模块ID的路径 js/lib baseUrl: 'js/lib', //另外,若是模块ID以app开始, //它会从js/app目录加载。paths设置时相对于baseUrl的,毫不会包括“.js”的,可是paths设置能够是相对directory的 paths: { app: '../app' } });
开始main app的逻辑。
requirejs(['jquery', 'canvas', 'app/sub'], function ($, canvas, sub) { //jQuery, canvas and the app/sub module are all //loaded and can be used here now. });
在这个例子中,第三方库(vendor实际上是供应商的意思)如jQuery,并无将它的版本号显示在文件名中。若是你想跟踪版本号,建议新开一个单独的文件来记录,或者你能够用一些工具,像volo,能够将package.json打上版本信息,但文件名仍是jQuery.js。这有助于你的配置最小化,避免为每一个版本的库设置paths路径。例如,将"jquery"配置成(configure)“jquery-1,7,2"canvas
理想状态下(ideally),每一个加载的脚本都是经过define()来定义的一个模块。然而,有些"浏览器全局变量注入"型传统/遗留(legacy)浏览器可能不能用define()来定义它们的依赖关系。为此(for those),你能够用shim config来解析它们的依赖关系。
若是你不想解析它们的依赖关系,你可能会获得一些加载错误,基于速度的缘由(for speed),requireJs会异步( asynchronously)、无序(out of order)地加载脚本。
data-main属性是一个特殊属性,require.js在加载脚本的时候会检查(check)它:
<!--当require.js加载的时候,它会忽视script/main.js的其余script标签属性--> <script data-main="scripts/main" src="scripts/require.js"></script>
你能够在data-main中设置配置选项,而后加载你的第一个应用模块(application module)。注意:require.js的标签加载的模块是异步的async attribute。这意味着,若是你在这个页面加载了其余scripts,则不能保证经过require.js加载的页面能够先于这些脚本加载完毕。
举个例子,如下的构造不能保证foo模块的require.config的路径设置会先于require()foo模块执行:
<script data-main="scripts/main" src="scripts/require.js"></script> <script src="scripts/other.js"></script>
// contents of main.js: require.config({ paths: { foo: 'libs/foo-1.1.3' } });
// contents of other.js: //这段代码可能会在main.js 的require.config()以前执行。若是这放生了,require.js会加载'scripts/foo.js‘额不是'scripts/libs/foo-1.1.3.js
require(['foo'], function(foo) { });
若是你想在HTML页面中调用require(),最好不要用data-main。data-main只用在页面只须要一个入口的时候。若是页面想在行内调用require(),最好以下所示书写代码
<script src="scripts/require.js"></script> <script> require(['scripts/config']), function() { // Configuration loaded now, safe to do other require calls // that depend on that config. require(['foo'], function(foo) { }); }); </script>
一个模块不一样于传统脚本文件的地方是,它定义了一个模块范围来避免污染全局环境。它明确地列举了依赖的文件,并以函数(定义那个模块的函数)参数的形式将这些依赖注入。RequireJs的模块是Module Pattern的一个扩展,这样的好处是不须要全局地引入其余模块。
RequireJs的模块属性让它们能够尽快地被加载,就算加载是无序的,依赖也会按照争取的顺序。由于没有建立全局变量,因此在一个页面中能够建立多个版本的模块load multiple versions of a module in a page.
(若是你熟悉或者使用过commonjs,那么请看 CommonJS Notes来了解requireJs和CommonJs的映射(map to)关系)
一个磁盘文件应该只定义一个模块。optimization tool工具能够将模块分组优化(grouped into optimized bundles).
若是一个模块没有任何依赖,仅含任何值/对(name/value),则在define()中定义这些值/对就行了。
//Inside file my/shirt.js: define({ color: "black", size: "unisize" });
若是以个模块没有依赖,可是须要一些函数来作一些setup的工做,那就在define中定义该函数:
//my/shirt.js now does setup work //before returning its module definition. define(function () { //Do setup work here return { color: "black", size: "unisize" } });
若是一个模块有依赖,第一个参数(arguments)应该是一串依赖名的数组,第二个参数应该是定义的函数。一旦全部的依赖加载完毕,这个函数就会被调用来定义该模块。定义这个模块的函数应该返回一个对象。依赖会以一个参数的形式传给函数,参数列表和依赖名称列表一一对应
//my/shirt.js 有一些依赖, cart和inventory,都和shirt.js在同一个目录下 define(["./cart", "./inventory"], function(cart, inventory) { //return an object to define the "my/shirt" module. return { color: "blue", size: "large", addToCart: function() { inventory.decrement(this); cart.add(this); } } } );
这个函数有两个参数“cart”和“inventory”。对应的模块以模块名"./cart" 和"./inventory"展现。
这个函数会在my/cart ,my/inventory被加载后调用,而且得到cart和inventory的函数参数。
严重不容许模块定义全局的变量,这样,多版本的模块才能存在于同一个页面。另外,函数的参数顺序应该和依赖模块的顺序同样。
返回的对象定义了"my/shirt"模块。这样定义模块,"my/shirt"就不会以全局对象的方式存在。
模块的并不必定要有返回值。任何函数的返回值(return value)都是容许的。此处有一个模块如它所定义地返回了一个函数:
/A module definition inside foo/title.js. It uses //my/cart and my/inventory modules from before, //but since foo/title.js is in a different directory than //the "my" modules, it uses the "my" in the module dependency //name to find them. The "my" part of the name can be mapped //to any directory, but by default, it is assumed to be a //sibling to the "foo" directory. define(["my/cart", "my/inventory"], function(cart, inventory) { //return a function to define "foo/title". //It gets or sets the window title. return function(title) { return title ? (window.title = title) : inventory.storeName + ' ' + cart.name; } } );
若是你想从新用一些以CommonJS module format的方式写的代码,而这些代码难以用上述的依赖数组的方式来写,你能够考虑直接将这些依赖对应到本地变量。你可使用一个简单的commonJs包装来实现simplified CommonJS wrapper:
define(function(require, exports, module) { var a = require('a'), b = require('b'); //Return the module value return function () {}; } );
这个包装依赖于Function.prototype.toString()将函数内容赋予有意义的字符串。这PS3或者一些老的Opera手机浏览器上表现很差。用optimizer将这些依赖变成一个数组的格式,以便与在这些设备上用。
更多的信息参考 CommonJS page, "Sugar" section in the Why AMD page.
你可能会看到一个define()中包含了模块名做为第一个参数:
//Explicitly defines the "foo/title" module: define("foo/title", ["my/cart", "my/inventory"], function(cart, inventory) { //Define foo/title object in here. } );
这些一般是optimization tool生成的。你能够本身命名模块名,但这会使模块更难移植——若是你将文件夹移到另外一个目录中,你还得改它们的名字。最好避免对模块硬编码,而让optimization tool来生成模块名。这个工具须要生成模块名,以便将这些模块打成一个包(be bundled),使浏览器更快加载。
One module per file.: 每一个js文件只能定义一个模块,这是模块寻找机制的天然要求。多个模块会被 optimization tool打包成一个模块,但你须要用将多个模块放到一个文件夹里.
Relative module names inside define(): 对于define()中("./relative/name")的模块调用,确保将require自己做为一个依赖注入到模块中, 这样,相对路径的名称才能被正确地解析:
define(["require", "./relative/name"], function(require) { var mod = require("./relative/name"); });
更好的方式是,像commomjs同样使用一个更短的解析:
define(function(require) { var mod = require("./relative/name"); });
这个模块利用Function.prototype.toString()去寻找require()的调用,并把它们加到依赖的数组中。在require()的工做路径下,代码会被正确的解析。
若是你在一个目录中建立了不少模块,相对路径一般是颇有用的。你分享这个目录给其余人,你也能够对这个目录中的其余模块作一些改动,而没必要知道模块的名称。
Generate URLs relative to module: 你可能须要生成一个相对于模块的URL. 你能够将require做为一个依赖注入,而后用require.toUrl()来生成一个url:
define(["require"], function(require) { var cssUrl = require.toUrl("./style.css"); });
Console debugging: 若是你想调用一个已经经过 require(["module/name"] , function(){}) 方式加载的模块,你能够用字符串做为模块名的参数来调用它:
require("module/name").callSomeFunction()
注意,这个只对于已经用"module/name"方式异步加载的模块有效。若是用一个相对路径,像'./module/name',那只会在define内部工做有效。
若是你定义了一个循环依赖("a" needs "b" and "b" needs "a"), 那么当"b"模块被调用的时候, 它会获得一个未定义的a值。“b”能够在已经被require()定义好后来获取“a”(确保将require做为一个依赖注入):
//Inside b.js: define(["require", "a"], function(require, a) { //"a" in this case will be null if "a" also asked for "b", //a circular dependency. return function(title) { return require("a").doSomething(); } } );
一般,你不须要用require()来获取模块, 只要将依赖的模块做为参数传给函数就能够了.循环依赖是不多出现的,若是出现了,你就想从新考虑设计问题了。然而,有时候也是须要循环依赖的,若是这样的话,能够考虑上述的代码组织。
若是你很是熟悉commonJs,你也能够用exports的来制造一个空模块,这也能够以依赖的形式被其余模块获取。这样作的话,你能够在循环依赖的任意一方安全地使用其余模块。.这只在每一个模块都以object做为模块值输出的时候有效,函数形式无效:
//Inside b.js: define(function(require, exports, module) { //If "a" has used exports, then we have a real //object reference here. However, we cannot use //any of "a"'s properties until after "b" returns a value. var a = require("a"); exports.foo = function () { return a.bar(); }; });
或者,若是你再用数组做为依赖,能够调用特殊的exports模块 'exports' dependency:
//Inside b.js: define(['a', 'exports'], function(a, exports) { //If "a" has used exports, then we have a real //object reference here. However, we cannot use //any of "a"'s properties until after "b" returns a value. exports.foo = function () { return a.bar(); }; });
JSONP 是javascript中服务调用的一种方式。它经过script标签发起HTTP GET请求,是实现跨域的一种公认手段。
要在requireJs使用JSONP,要在回调中将参数属性设置为”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); } );
这种JSONP的用法仅限于JSONP服务的初始化initial application setup. 一旦JSONP服务超时,意味着其余经过define()的模块不会执行,因此这种错误处理是不强健的。
Only JSONP return values that are JSON objects are supported.一个JSONP的返回是一个数组,若是是一个字符串或者数字式不会工做的。
这个机制不能被用在long-polling 类的JSONP链接-- 那些用来处理实时流的APIs. 这种类型的api在收到每一个返回的时候通常会作script清理,RequireJS只会获取一次JSONP URL—— 后继使用require()或者define()发起的URL会获得一个缓存值。
加载JSONP服务的错误一般以服务超时的形式出现。由于简单加载一个script标签不会获得不少网络错误信息。你能够用override requirejs.onError() 来获取错误。更多信息参见 Handling Errors section.
有一个全局的函数requirejs.undef(), 用来undefining一个模块.它会重设加载器的内部(internal)状态来消除前一个模块的定义。
然而,若是一个模块已经被定义了,而且在其余模块中做为一个依赖被调用执行了,全局函数是不会清除这个模块的。 因此它仅在无其余模块持有该模块错误的时候有用,或者当将来须要加载这模块时有点用。 See the errback section for an example.
若是想知道更多关于undefined的复杂语法,the semi-private onResourceLoad API may be helpful.