用法§ 1
加载 JavaScript 文件§ 1.1
相比于传统的用<script>标签来加载js,RequireJS 采用了不同的方式。 它的目标是促进JS编程模块化。 虽然它也能优化我们JS程序的性能, 但是它的主要目的还是促进JS编程模块化。其中, 它鼓励使用 module IDs 来加载JS文件,而不是 用<script>标签直接引入JS文件的URLs的方式。
RequireJS 基于 baseUrl 配置的地址来加载所有JS文件. baseUrl 默认被设置成通过data-main属性引入的脚本的相同目录. data-main 属性是require.js指定义的属性,require.js会自动检测这个属性来开始加载JS.如下例:
- <--这样将默认设置 baseUrl 为 "scripts" 目录-->
- <script data-main="scripts/main.js" src="require.js"></script>
或者, baseUrl 能在 RequireJS config 中进行配置. 如果又没配置,又没使用data-main属性,baseUrl 会被设置为运行RequireJS的HTML页面的目录。
RequireJS 假定所有依赖关系都是脚本, 所以可以省略module IDs后面的 ".js" 后缀. 在将module IDs转换成JS文件路径的时候,RequireJS 会自动补上的".js"。在 paths config 中, 你可以设置一组脚本(译者注:我称这个为包,类似JAVA中的package)的地址.相比于传统的用<script>标签,这些功能使你能使用更简单的字符串来加载JS。
你可以能很多次的想要直接引入JS,并且不使用"baseUrl + paths"规则。那么请看这里,如果一个module IDs符合以下规则,那么将不会通过"baseUrl + paths"配置来生成路径,而是会将module IDs直接当作文件路径:
- 以 ".js"结尾.
- 以 "/"开头.
- 包含URL协议, 例如 "http:" 、 "https:".
一般来说,使用"baseUrl + paths"配置来设置module IDs路径更好.这样更灵活,可配置,更容易重命名和更改路径。
不过,为了避免一堆配置,应该尽量避免使用很深层次的文件夹。如果你想把你的类库和应用独有的代码分开放,你可以使用如下文件夹结构:
www/
- index.html
- js/
- app/
- sub.js
- lib/
- jquery.js
- canvas.js
- app.js
- app/
- index.html
- js/
- app/
- sub.js
- lib/
- jquery.js
- canvas.js
- app.js
- app/
index.html:
- <script data-main="js/app.js" src="js/require.js"></script>
app.js:
- requirejs.config({
- //By default load any module IDs from js/lib
- baseUrl: 'js/lib',
- //except, if the module ID starts with "app",
- //load it from the js/app directory. paths
- //config is relative to the baseUrl, and
- //never includes a ".js" extension since
- //the paths config could be for a directory.
- paths: {
- app: '../app'
- }
- });
- // Start the main app logic.
- requirejs(['jquery', 'canvas', 'app/sub'],
- function ($, canvas, sub) {
- //jQuery, canvas and the app/sub module are all
- //loaded and can be used here now.
- });
注意例子中的类库文件jQuery 没有在文件名中包含版本信息。我们推荐在一个单独的文件中存储版本信息。这样将最小化配置信息长度。
理想情况下,你用来当作模块而加载的脚本应该调用 define()来定义。然后,你可以能会有一些以前的,没有通过define()来描述依赖关系的脚本。那么你可以通过 shim config来配置依赖关系。
如果你不配置好依赖关系,可能会导致运行错误。
定义模块§ 1.2
模块跟传统的JS文件不同的是,它是一个封装良好的、定义在内部作用域的对象,这样可以避免与全局命名空间的冲突。它可以明确的列出依赖模块,并且无需将依赖模块当全局对象引入,而是将依赖项当参数传给定义模块的函数。RequireJS中的模块都是Module Pattern的扩展, 其优势是不需要全局引用其他模块。
RequireJS的模块会尽可能快的加载,甚至不会按照顺序加载, 但是它会判断正确的依赖顺序来运行模块。并且因为不会创建全局对象, 它可以 在一个页面中加载一个模块的多个版本。
(如果你很熟悉或者正在使用CommonJS modules, 那么关于如何将CommonJS的模块格式转换成RequirejS模块格式,请看 CommonJS Notes )。
一个JS文件只能定义一个模块。使用 optimization tool 可以将多个模块优化合并成一个文件。
简单键值对§ 1.2.1
如果模块没有依赖关系,只是简单的键值对的集合,那么传一个对象给 define():
//Inside file my/shirt.js:
define({
color: "black",
size: "unisize"
});
定义函数§ 1.2.2
如果模块没有依赖关系, 只是需要用到一个函数来设置一些东西,那么传一个函数给 define():
//my/shirt.js now does setup work
//before returning its module definition.
define(function () {
//Do setup work here
return {
color: "black",
size: "unisize"
}
});
定义有依赖关系的函数§ 1.2.3
如果模块有依赖模块, 第一个参数是包含依赖模块名字的数组,第二个参数是要定义的函数(为了方便描述,下面称这个函数为模块构造函数)。模块构造函数会在依赖模块加载完成后执行,它要返回一个定义好的模块对象。 依赖模块会被当参数传递给模块构造函数,参数的顺序和依赖模块数组数顺序相同:
//my/shirt.js now has some dependencies, a cart and inventory
//module in the same directory as 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);
}
}
}
);
本例中, 创建了模块 my/shirt . 它依赖模块 my/cart 和 my/inventory。 在硬盘上,文件结构如下:
- my/cart.js
- my/inventory.js
- my/shirt.js
模块构造函数包含两个参数 "cart" 和 "inventory"。 这两参数代表模块名为 "./cart" 和"./inventory" 的模块。
模块构造函数只会在依赖模块加载完成才执行,并且它接收模块 "cart" 和"inventory" 当参数。
将模块定义为全局对象是不推荐的,这会不能加载一个模块的多个版本。 模块构造函数的参数顺序要跟依赖模块顺序一致。
模块构造函数返回的对象定义了模块 "my/shirt" 。通过这种方式定义模块, "my/shirt"就不是一个全局对象了。
定义模块为一个函数§ 1.2.4
模块构造函数不一定是返回一个对象。 任何有效的返回值都可以。下面是模块返回一个函数:
//A module definition inside foo/title.js. It uses
//my/cart and my/inventory modules from before,
//but since foo/bar.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 简化包装器定义模块§ 1.2.5
用传统 CommonJS 模块格式 写的代码,因为很难维护依赖关系,所以很难重用代码。你可能更愿意用局部变量来维护依赖关系,那么你可以用 CommonJS 简化包装器:
define(function(require, exports, module) {
var a = require('a'),
b = require('b');
//Return the module value
return function () {};
}
);
不过这个包装器依赖于 Function.prototype.toString() 方法来分析模块构造函数的文本,以此获取依赖关系。而这个在某些设备上是不支持的,如 PS3 和一些老版本的Opera手机浏览器。可以使用 优化程序 来为这些设备分析依赖关系。
更多有用的信息可以看这里 CommonJS page, "Sugar" section in the Why AMD page。
定义自定义名字的模块§ 1.2.6
你可以遇到一些 define() 调用的第一个参数是模块名字:
//Explicitly defines the "foo/title" module:
define("foo/title",
["my/cart", "my/inventory"],
function(cart, inventory) {
//Define foo/title object in here.
}
);
这通常都是由 优化工具 生成的代码。 你也可以自己定义一个模块名,不过这样使模块可移植性不行 -- 如果你移动模块文件到另外一个目录,你需要改模块名。 最好避免这样的硬编码,让 优化工具 自动填上模块名。优化工具 自动填上模块名是为了能把多个模块合并到一个文件中来优化浏览器的加载速度。
其他注意事项§ 1.2.7
一个文件一个模块: 一个JS文件中只定义一个模块, 鉴于RequireJS中的模块名字-文件路径查找算法。 多个模块可以合并到一个文件中,但是你只应该用 优化工具 来做这个操作。
在define()中使用模块的相对路径: 引入模块的代码require("./relative/name"), 也可以写在模块构造函数里,但是必须将 "require"当作依赖模块:
define(["require", "./relative/name"], function(require) {
var mod = require("./relative/name");
});
下面的写法更好、更简短,它使用了 translating CommonJS 模块:
define(function(require) {
var mod = require("./relative/name");
});
这种方式会使用 Function.prototype.toString() 来获取模块构造函数的函数体字符串,来分析查找调用require(moduleName)的地方,并把moduleName加到依赖数组中,如果使用的是模块名是相对路径,那么代码就可以正确的运行。
如果你在一个目录下创建比较少的模块,相对路径很好用。这样你可以和其他人或者其他项目共享这个目录, 你可以调用该目录下的模块而不需要知道这个目录的名称。
生成模块的相对URL: 如果你需要生成模块的相对URL,引入"require"为依赖模块,并且使用require.toUrl() 方法来生成:
define(["require"], function(require) {
var cssUrl = require.toUrl("./style.css");
});
调试控制台: 如果你需要在控制台中调试通过require(["module/name"], function(){}) 方式加载过的模块,可以调用 require("module/name")的方式来获取这个模块:
require("module/name").callSomeFunction()
注意:这只能在"module/name"已经通过异步方式require(["module/name"])加载过的情况下使用。如果使用的是相对路径'./module/name',那这个模块还是只能在define内用。
Circular Dependencies§ 1.2.8
如果你定义一个循环依赖关系 (a 依赖b 并且 b 依赖 a),那么当b的模块构造函数被调用的时候,传递给他的a会是undefined。 但是b可以在a模块在被引入之后通过require(‘a’)来获取a (一定要把require作为依赖模块,RequireJS才会使用正确的 context 去查找 a):
//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()的方式来获取一个模块,而是使用传递给模块构造函数的参数。循环依赖很罕见,通常表明,你可能要重新考虑这一设计。然而,有时需要这样用,在这种情况下,就使用上面那种指定require()的方式吧。
如果你熟悉 CommonJS 模块的写法,你也可以使用 exports 创建一个空对象来导出模块,这样定义的模块可以被其他模块立即使用。即使在循环依赖中,也可以安全的直接使用。 不过这只适用于导出的模块是对象,而不是一个函数:
//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'当作依赖模块:
//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 服务为依赖模块§ 1.2.9
JSONP 是一种使用JS调用某些服务的方法。这是一个公认的解决跨域调用服务的方案,它只需要通过HTTP GET一段包含script标签的脚本。
只需要把回调函数参数值设为使用"define" ,就能用 RequireJS来实现JSONP。这意味着,你可以用JSONP的方式来获取一个模块。
下面是一个调用 JSONP API 的例子。 例中, JSONP 回调函数的参数名是"callback","callback=define" 告诉API,返回一个用"define()"包装的脚本:
require(["http://example.com/api/data.json?callback=define"],
function (data) {
//The dta object will be the API response for the
//JSONP data call.
console.log(data);
}
);
最好只在初始化应用设置的时候使用这种方法。因为如果JSONP 服务超时,那其他通过define() 定义的模块将不会执行,并且异常处理也不能很好的运行。
JSONP 返回的值必须是一个Object。Array、String、Number都不行。
这种方式也不能用于长轮询式的连接 -- 这种APIs都会进行实时流处理。这种APIs在接收到每个响应后都需要清除缓存,而对于同一个 JSONP URL, RequireJS 只会请求一次-- 随后的都会使用缓存。
JSONP 服务在加载中的异常目前只有服务超时, 因为 script 标签加载没有提供更多网络异常信息。为了检测异常,你可以重写 requirejs.onError() 。这里是是关于 异常处理 的更多信息。
卸载一个模块§ 1.2.10
全局方法requirejs.undef(), 可以用来卸载一个模块。它会重置内部加载器内部的状态来卸载之前加载过的模块。
不过,它不会卸载那些被其他以加载模块依赖的模块。 所以这个方法只在要卸载的模块不会被其他模块依赖的情况下有用。请看例子 errback section 。
如果你想做更复杂的依赖关系分析图, 请看半私有方法 onResourceLoad API .