一个复杂的应用,其编程语言也必须要有模块机制,方能更好的组织代码。在前端 js 模块中,RequireJS 是一个非常小巧的 JavaScript 模块载入框架,是 AMD 规范最好的实现者之一。
当我们引入 js 文件时,不会阻塞浏览器对 html 文档的渲染。
我们不用在当心 js 文件的引入顺序,RequireJS 会自动帮我们处理依赖关系。
建议所有的依赖在模块开始就全部声明加载,不过现在也提供就近加载的方法。
其实 RequireJS 的使用非常简单,核心只有两个函数:
下载方式:
js/lib/index.js
(function () {
alert('Hello index')
})()
index.html
<script src="scripts/require.js"></script>
<script>
require(["js/lib/index.js"], function () {
// do something after loading index.js
})
<script>
如果有些路径过长,有时候我们希望能使用别名代替,这个时候可以使用 requirejs.config
函数。
<script src="scripts/require.js"></script>
<script>
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: {
"jquery" : ["http://libs.baidu.com/jquery/2.0.3/jquery"]
}
})
require(["index.js", "jquery"], function (index, $) {
// do something after loading index.js
})
<script>
如果我们页面过多,总是写 requirejs.config
不太好,感觉 ugly。RequireJS 为我们提供了一种方式。
<script data-main="js/app.js" src="js/require.js"></script>
在 script 标签中,加入 data-main 属性。这样会默认加载 app.js,而我们只需要在 app.js 中填写配置信息即可。
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: {
"jquery" : ["http://libs.baidu.com/jquery/2.0.3/jquery"]
}
})
require(["index.js", "jquery"], function (index, $) {
// do something after loading index.js
})
通过 require 加载的模块一般都需要符合 AMD 规范,即使用 define 来申明模块。但是部分时候需要加载非 AMD 规范的 js,这时候就需要用到另一个功能:shim。
比如我要是用 underscore 类库,但是他并没有实现 AMD 规范,那我们可以这样配置:
// 配置
require.config({
shim: {
"underscore" : {
exports : "_"
}
}
})
// 使用
require(["underscore"], function(_){
_.each([1,2,3], alert)
})
本质上来说,就是做了一次 RequireJS 的模块定义。
这个函数用来定义模块。我试着思考一下,如果设计一个模块新系统。无非就是三块:
这里我们不对其中的原理进行深入探究,先从简单的使用说起。define 顾名思义,就是用来定义一个模块的。
define(id?, dependencies?, factory)
其中 ?
表示该参数可选
define({
color: "black",
size: "unisize"
})
这样定义的对象如果被多处引用,那么引用的地方会共享这个对象的引用。
define(function () {
//Do setup work here
return {
color: "black",
size: "unisize"
}
})
通过函数返回一个对象的副本,引用的地方对对象的修改不会相互影响,这是一种典型的工厂模式。
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 模块,然后通过回调函数的参数,可以获取两个模块的引用。
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
}
})
这个模块最后返回的值是一个函数,而非一个对象。
define("foo/title", ["my/cart", "my/inventory"], function(cart, inventory) {
//Define foo/title object in here.
})
理论上来讲,我们定义一个模块是需要名字的。但是实际中,一个模块就是一个文件,而文件名就是模块的名字,并且我们会在 require.config()
中定义别名。所以一般我们是不需要给模块加名字的。
define(["require", "./relative/name"], function(require) {
var mod = require("./relative/name");
});
define(function(require) {
var mod = require("./relative/name");
});
这种写法应该说和 RequireJS 的初衷相违背,因为 RequireJS 提倡依赖前置,意思是说模块中会用到的依赖在模块最初就加载好,而不是在会用到的地方在加载。
举例来说:你有个模块有 800 行代码,但所依赖的其他模块在 500 行才会有用到。可能你希望,在 500 行的地方再加载所依赖的模块。这种情况叫就近依赖,CMD 规范就是如此。不过现在 RequireJS 也支持就近依赖了。
require(dependencies?, factory)
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)
}
)
观察 require 和 define 函数,我们可以发现,它们有一个参数的差异。require 没有 id 参数。
老实说这个 require 和 define 函数内部机制差不多,不一样的地方是 define 的回调函数需要有 return 语句返回模块对象,这样 define 定义的模块才能被其他模块引用;require 的回调函数不需要 return 语句。
实际中,我们只会在顶层模块(不需要被其他模块调用)才会使用 require 函数。
参考