今天和一个朋友聊天的时候,谈到前端的模块化加载RequireJS,我瞬间短路了,我之前貌似没有用过RequireJS。朋友又问,那你们之间的模块化加载是怎么做的?我想了想,我们之前的很多项目大都是MVC 结构的。前端这块主要是控制器和视图,一般是即用即引,由于复杂度不够,没有搞什么模块化。但是我后来百度了一下,发现我对RequireJS并不陌生,很多项目中潜移默化的已经应用了,但是我没有注意罢了!例如,nodejs的引用方式和RequireJS差不多,nodejs集成的貌似是CommonJS。EXTJS5.0中,貌似集成了RequireJS。我之前在写EXTJS的时候,感觉好多写法就是RequireJS的写法(不是很确定,我也没有重新翻开extjs5.0进行查看验证,要是有说的不对的地方,大家留言指正一下!)
本文主要是介绍RequireJS模块化加载,引进RequireJS,并不对其做深入研讨,因为我本身对RequireJS了解的不是很深入。引入RequireJS模块化加载,为后续继续学习打下基础。
即使您的 Web 前端由于种种历史原因还没有打算进行模块化的设计,RequireJS 作为 JavaScript 文件的加载器,还是可以帮助您来完成异步非阻塞的文件加载。
如案例一 所示,有两个 JavaScript 文件 a.js 和 b.js,里面各自定义了 myFunctionA 和 myFunctionB 两个方法,通过下面这个方式可以用 RequireJS 来加载这两个文件,在 function 部分的代码可以引用这两个文件里的方法。
require(["./js/a.js","./js/b.js"],function(){
myFunctionA();
myFunctionB();});
require 方法里的这个字符串数组参数可以允许不同的值,当字符串是以”.js”结尾,或者以”/”开头,或者就是一个 URL 时,RequireJS 会认为用户是在直接加载一个 JavaScript 文件,否则,当字符串是类似”my/module”的时候,它会认为这是一个模块,并且会以用户配置的 baseUrl 和 paths 来加载相应的模块所在的 JavaScript 文件。配置的部分会在稍后详细介绍。
这里要指出的是,RequireJS 默认情况下并没有保证 myFunctionA 和 myFunctionB 一定是在页面加载完成以后执行的,在有需要保证页面加载以后执行脚本时,RequireJS 提供了一个独立的 domReady 模块,需要去 RequireJS 官方网站下载这个模块,它并没有包含在 RequireJS 中。有了 domReady 模块,案例一 的代码稍做修改加上对 domReady 的依赖就可以了。
require(["domReady!","./js/a.js","./js/b.js"],function(){
myFunctionA();
myFunctionB();});
执行案例二的代码后,通过 Firebug 可以看到 RequireJS 会在当前的页面上插入为 a.js 和 b.js 分别声明了一个 < script> 标签,用于异步方式下载 JavaScript 文件。async 属性目前绝大部分浏览器已经支持,它表明了这个 < script> 标签中的 js 文件不会阻塞其他页面内容的下载。
使用 RequireJS 来定义 JavaScript 模块
这里的 JavaScript 模块与传统的 JavaScript 代码不一样的地方在于它无须访问全局的变量。模块化的设计使得 JavaScript 代码在需要访问”全局变量”的时候,都可以通过依赖关系,把这些”全局变量”作为参数传递到模块的实现体里,在实现中就避免了访问或者声明全局的变量或者函数,有效的避免大量而且复杂的命名空间管理。
如同 CommonJS 的 AMD 规范所述,定义 JavaScript 模块是通过 define 方法来实现的。
下面我们先来看一个简单的例子,这个例子通过定义一个 student 模块和一个 class 模块,在主程序中实现创建 student 对象并将 student 对象放到 class 中去。
define(function(){return{
createStudent:function(name, gender){return{
name: name,
gender: gender
};}};});
define(function(){var allStudents =[];return{
classID:"001",
department:"computer",
addToClass:function(student){
allStudents.push(student);},
getClassSize:function(){return allStudents.length;}};});
require(["js/student","js/class"],function(student, clz){
clz.addToClass(student.createStudent("Jack","male"));
clz.addToClass(student.createStudent("Rose","female"));
console.log(clz.getClassSize());// 输出 2 });
student 模块和 class 模块都是独立的模块,下面我们再定义一个新的模块,这个模块依赖 student 和 class 模块,这样主程序部分的逻辑也可以包装进去了。
define(["js/student","js/class"],function(student, clz){return{
addNewStudent:function(name, gender){
clz.addToClass(student.createStudent(name, gender));},
getMyClassSize:function(){return clz.getClassSize();}};});
require(["js/manager"],function(manager){
manager.addNewStudent("Jack","male");
manager.addNewStudent("Rose","female");
console.log(manager.getMyClassSize());// 输出 2 });
通过上面的代码示例,我们已经清楚的了解了如何写一个模块,这个模块如何被使用,模块间的依赖关系如何定义。还是有一些使用技巧需要提示一下:
尽量不要提供模块的 ID,如 AMD 规范所述,这个 ID 是可选项,如果提供了,在 RequireJS 的实现中会影响模块的可迁移性,文件位置变化会导致需要手动修改该 ID。
每个 JavaScript 文件只定义一个模块,模块名称和文件路径的查找算法决定了这种方式是最优的,多个的模块和文件会被优化器进行优化。 避免模块的循环依赖,如果实在避免不了,可以模块中加上对”require”模块的依赖,在代码中直接用
require(”dependencyModuleName”)
前面的介绍中,我们似乎忽略了一个基本问题,模块名字是怎么来的?当我在 require 一个模块时,这个模块是如何映射到具体的 JavaScript 文件上去?这就涉及到如何配置 RequireJS。
最简化的加载 RequireJS 的方式如案例2 所示,在这种情况下,我们没有指定一个 baseUrl 和 paths 给 RequireJS,如果通过如案例10 所示方式,则 data-main 指定了一个在当前 index.html 目录并行的文件夹下的 /js/main.js 作为程序入口,而 /js 目录也将作为 baseUrl 在其他模块定义时候使用。
因此,我们前面示例中的所有模块依赖,都可以去掉”js/”,直接写 ”student”, ”class”,”manager” 等。 一种更为直接的方式显示指定 baseUrl 和 paths 就是利用 require.config 来设置这些参数。如案例十 所示。
baseUrl指明的是所有模块的 base URL,比如”my/module”所加载的 script实际上就是 /js/my/module.js。注意,以 .js 结尾的文件加载时不会使用该 baseUrl,它们仍然会使用当前 index.html所在的相对路径来加载,所以仍然要加上”./js/”。如果 baseUrl没有指定,那么就会使用 data-main中指定的路径。
paths 中定义的路径是用于替换模块中的路径,如上例中的 some/module 具体的 JavaScript 文件路径是 /js/some/v1/module.js 。 waitSeconds 是指定最多花多长等待时间来加载一个 JavaScript 文件,用户不指定的情况下默认为 7 秒。
另外一个重要的配置是 packages,它可以指定其他符合 CommonJS AMD 规范的目录结构,由此带来了丰富的扩展性。如 Dojo、jQuery 等的模块也可以通过该配置来让 RequireJS 加载。
其他可配置的选项还包括 locale、context、deps、callback等,有兴趣的读者可以在 RequireJS 的官方网站查阅相关文档。
当 RequireJS 与其他框架一起工作的时候,显然它是可以作为统一的加载器来加载其他框架。鉴于 jQuery、Dojo 等已经支持了 AMD ,那么就有可能在定义自己的模块的时候,通过适当配置,直接把其他框架的模块作为依赖对象。
RequireJS 和 Dojo
案例十是一个基本的 RequireJS 和 Dojo 集成配置方法,这里关键是将 dojo 和 dijit 都在 packages 里面注册一下。
require ={
packages:[{
name:“dojo”,
location:“dojo”,
main:”lib/main-browser”,
lib:“.”},{
name:“dijit”,
location:“dijit”,
main:”lib/main”,
lib:“.”}],
paths:{
require:“./js”},
ready:function(){
require([“my/module”],function(module){});}};
在定义 module 这个模块时就可以直接将 dojo 和 dijit 里的模块作为依赖对象了。
RequireJS 和 jQuery
把 jQuery 作为一个依赖模块来使用也非常简单,只需要在 RequireJS 里的 config 里做相应的配置就可以了。
require.config({
paths:{"jquery":"./js/jquery-1.7"}});require(["jquery"],function(jQ){
console.log(jQ);});
RequireJS 和 Web Workers
Web Workers 是多线程的 JavaScript,每个 worker 里面的脚本都 会启动一个新的线程来执行,通过在 worker 里面用 importsScript 来加载 require.js 可以直接在 worker 里面使用 RequireJS 。
importScripts('./js/require.js');require(["require"],function(require){
postMessage("test");});
/public
|------require.js、config.js、index.html
|-------lib/
|-------a.js、b.js
|-------orthers/
|-------c.js
require.js:
其中requirejs的核心代码就是require.js文件,可以从官方网站上下载:
config.js:
用于配置requirejs的相关内容,可以设置文件目录,加载模块命名匹配,以及一些依赖关系等等。
index.html:
我们的测试页面或者网址首页。
/lib/a.js和/lib/b.js 以及 /others/c.js
是测试的模块js文件。
index.html
<doctype html><html><meta http-quiv="Content-Type" content="text/html;charset=UTF-8"><script data-main="config" src="require.js">script>html><body><h1>This is test for RequireJSh1><script type="text/javascript"> alert("index"); script>body>
其中,data-main指定主要的配置文件;src为requirejs的文件。
config.js
require.config({ // baseUrl:'/', paths:{ "a":"lib/a", "b":"lib/b", "c":"others/c" } }); require(['a','b','c'],function(a,b,c){ a.hello(); b.hello(); c.hello(); });
baseUrl指定所有文件的主要目录,paths配置模块名字以及其匹配的加载路径。
当有需要使用某些模块时,就可以通过require([xxx],function(xxx){xxx});的方式使用。
a.js
define([], function() { return { hello: function() { alert("hello, a"); } } });
b.js
define([], function() { return { hello: function() { alert("hello, b"); } } });
c.js
define([], function() { return { hello: function() { alert("hello, c"); } } });
模块化文件,一般是一个功能是一个文件。文件的名字,就是上面requireconfig中配置的模块名字。require加载文件时,会自动加上.js后缀。
当某些模块依赖其他模块时,可以通过define([xxx],function(xxx){yyy});的方式添加依赖关系,require会在异步加载后,自动调整次序。