引子
今天和一个朋友聊天的时候,谈到前端的模块化加载RequireJS,我瞬间短路了,我之前貌似没有用过RequireJS。朋友又问,那你们之间
的模块化加载是怎么做的?我想了想,我们之前的很多项目大都是MVC
结构的。前端这块主要是控制器和视图,一般是即用即引,由于复杂度不够,没有搞什么模块化。但是我后来百度了一下,发现我对RequireJS并不陌生,
很多项目中潜移默化的已经应用了,但是我没有注意罢了!例如,nodejs的引用方式和RequireJS差不多,nodejs集成的貌似是
CommonJS。EXTJS5.0中,貌似集成了RequireJS。我之前在写EXTJS的时候,感觉好多写法就是RequireJS的写法(不是很
确定,我也没有重新翻开extjs5.0进行查看验证,要是有说的不对的地方,大家留言指正一下!)
RequireJS模块化加载
本文主要是介绍RequireJS模块化加载,引进RequireJS,并不对其做深入研讨,因为我本身对RequireJS了解的不是很深入。引入RequireJS模块化加载,为后续继续学习打下基础。
RequireJS 简介
RequireJS 是一个非常小巧的 JavaScript 模块载入框架,是 AMD 规范最好的实现者之一。最新版本的 RequireJS
压缩后只有 14K,堪称非常轻量。它还同时可以和其他的框架协同工作,使用 RequireJS 必将使您的前端代码质量得以提升。
目前最新版本的 RequireJS 1.0.8 在 IE 6+、Firefox 2+、Safari 3.2+、Chrome 3+、Opera
10+ 上都工作的很好。
使用 RequireJS 加载 JavaScript 文件
即使您的 Web 前端由于种种历史原因还没有打算进行模块化的设计,RequireJS 作为 JavaScript 文件的加载器,还是可以帮助您来完成异步非阻塞的文件加载。
如案例一 所示,有两个 JavaScript 文件 a.js 和 b.js,里面各自定义了 myFunctionA 和
myFunctionB 两个方法,通过下面这个方式可以用 RequireJS 来加载这两个文件,在 function
部分的代码可以引用这两个文件里的方法。
案例一: 加载 JavaScript 文件
<code><span class="pln"> </span><span class="tag"><script</span><span class="pln"> </span><span class="atn">src</span><span class="pun">=</span><span class="atv">"./js/require.js"</span><span class="tag">></script></span><span class="pln"> </span><span class="tag"><script></span><span class="pln"> require</span><span class="pun">([</span><span class="str">"./js/a.js"</span><span class="pun">,</span><span class="pln"> </span><span class="str">"./js/b.js"</span><span class="pun">],</span><span class="pln"> </span><span class="kwd">function</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln"> myFunctionA</span><span class="pun">();</span><span class="pln"> myFunctionB</span><span class="pun">();</span><span class="pln"> </span><span class="pun">});</span><span class="pln"> </span><span class="tag"></script></span></code>
require 方法里的这个字符串数组参数可以允许不同的值,当字符串是以”.js”结尾,或者以”/”开头,或者就是一个 URL
时,RequireJS 会认为用户是在直接加载一个 JavaScript
文件,否则,当字符串是类似”my/module”的时候,它会认为这是一个模块,并且会以用户配置的 baseUrl 和 paths
来加载相应的模块所在的 JavaScript 文件。配置的部分会在稍后详细介绍。
这里要指出的是,RequireJS 默认情况下并没有保证 myFunctionA 和 myFunctionB
一定是在页面加载完成以后执行的,在有需要保证页面加载以后执行脚本时,RequireJS 提供了一个独立的 domReady 模块,需要去
RequireJS 官方网站下载这个模块,它并没有包含在 RequireJS 中。有了 domReady 模块,案例一 的代码稍做修改加上对
domReady 的依赖就可以了。
案例二: 页面加载后执行 JavaScript
<code><span class="pln"> </span><span class="tag"><script</span><span class="pln"> </span><span class="atn">src</span><span class="pun">=</span><span class="atv">"./js/require.js"</span><span class="tag">></script></span><span class="pln"> </span><span class="tag"><script></span><span class="pln"> require</span><span class="pun">([</span><span class="str">"domReady!"</span><span class="pun">,</span><span class="pln"> </span><span class="str">"./js/a.js"</span><span class="pun">,</span><span class="pln"> </span><span class="str">"./js/b.js"</span><span class="pun">],</span><span class="pln"> </span><span class="kwd">function</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln"> myFunctionA</span><span class="pun">();</span><span class="pln"> myFunctionB</span><span class="pun">();</span><span class="pln"> </span><span class="pun">});</span><span class="pln"> </span><span class="tag"></script></span></code>
执行案例二的代码后,通过 Firebug 可以看到 RequireJS 会在当前的页面上插入为 a.js 和 b.js 分别声明了一个
< script> 标签,用于异步方式下载 JavaScript 文件。async 属性目前绝大部分浏览器已经支持,它表明了这个
< script> 标签中的 js 文件不会阻塞其他页面内容的下载。
案例三:RequireJS 插入的 < script>
<code><span class="pln"> </span><span class="tag"><script</span><span class="pln"> </span><span class="atn">type</span><span class="pun">=</span><span class="atv">"text/javascript"</span><span class="pln"> </span><span class="atn">charset</span><span class="pun">=</span><span class="atv">"utf-8"</span><span class="pln"> </span><span class="atn">async</span><span class="pun">=</span><span class="atv">""</span><span class="pln"> </span><span class="atn">data-requirecontext</span><span class="pun">=</span><span class="atv">"_"</span><span class="pln"> </span><span class="atn">data-requiremodule</span><span class="pun">=</span><span class="atv">"js/a.js"</span><span class="pln"> </span><span class="atn">src</span><span class="pun">=</span><span class="atv">"js/a.js"</span><span class="tag">></script></span></code>
使用 RequireJS 来定义 JavaScript 模块
这里的 JavaScript 模块与传统的 JavaScript 代码不一样的地方在于它无须访问全局的变量。模块化的设计使得
JavaScript
代码在需要访问”全局变量”的时候,都可以通过依赖关系,把这些”全局变量”作为参数传递到模块的实现体里,在实现中就避免了访问或者声明全局的变量或者
函数,有效的避免大量而且复杂的命名空间管理。
如同 CommonJS 的 AMD 规范所述,定义 JavaScript 模块是通过 define 方法来实现的。
下面我们先来看一个简单的例子,这个例子通过定义一个 student 模块和一个 class 模块,在主程序中实现创建 student 对象并将 student 对象放到 class 中去。
案例四: student 模块,student.js
<code><span class="pln"> define</span><span class="pun">(</span><span class="kwd">function</span><span class="pun">(){</span><span class="pln"> </span><span class="kwd">return</span><span class="pln"> </span><span class="pun">{</span><span class="pln"> createStudent</span><span class="pun">:</span><span class="pln"> </span><span class="kwd">function</span><span class="pun">(</span><span class="pln">name</span><span class="pun">,</span><span class="pln"> gender</span><span class="pun">){</span><span class="pln"> </span><span class="kwd">return</span><span class="pln"> </span><span class="pun">{</span><span class="pln"> name</span><span class="pun">:</span><span class="pln"> name</span><span class="pun">,</span><span class="pln"> gender</span><span class="pun">:</span><span class="pln"> gender </span><span class="pun">};</span><span class="pln"> </span><span class="pun">}</span><span class="pln"> </span><span class="pun">};</span><span class="pln"> </span><span class="pun">});</span></code>
案例五:class 模块,class.js
<code><span class="pln"> define</span><span class="pun">(</span><span class="kwd">function</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln"> </span><span class="kwd">var</span><span class="pln"> allStudents </span><span class="pun">=</span><span class="pln"> </span><span class="pun">[];</span><span class="pln"> </span><span class="kwd">return</span><span class="pln"> </span><span class="pun">{</span><span class="pln"> classID</span><span class="pun">:</span><span class="pln"> </span><span class="str">"001"</span><span class="pun">,</span><span class="pln"> department</span><span class="pun">:</span><span class="pln"> </span><span class="str">"computer"</span><span class="pun">,</span><span class="pln"> addToClass</span><span class="pun">:</span><span class="pln"> </span><span class="kwd">function</span><span class="pun">(</span><span class="pln">student</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln"> allStudents</span><span class="pun">.</span><span class="pln">push</span><span class="pun">(</span><span class="pln">student</span><span class="pun">);</span><span class="pln"> </span><span class="pun">},</span><span class="pln"> getClassSize</span><span class="pun">:</span><span class="pln"> </span><span class="kwd">function</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln"> </span><span class="kwd">return</span><span class="pln"> allStudents</span><span class="pun">.</span><span class="pln">length</span><span class="pun">;</span><span class="pln"> </span><span class="pun">}</span><span class="pln"> </span><span class="pun">};</span><span class="pln"> </span><span class="pun">}</span><span class="pln"> </span><span class="pun">);</span></code>
案例六: 主程序
<code><span class="pln"> </span><span class="kwd">require</span><span class="pun">([</span><span class="str">"js/student"</span><span class="pun">,</span><span class="pln"> </span><span class="str">"js/class"</span><span class="pun">],</span><span class="pln"> </span><span class="kwd">function</span><span class="pun">(</span><span class="pln">student</span><span class="pun">,</span><span class="pln"> clz</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln"> clz</span><span class="pun">.</span><span class="pln">addToClass</span><span class="pun">(</span><span class="pln">student</span><span class="pun">.</span><span class="pln">createStudent</span><span class="pun">(</span><span class="str">"Jack"</span><span class="pun">,</span><span class="pln"> </span><span class="str">"male"</span><span class="pun">));</span><span class="pln"> clz</span><span class="pun">.</span><span class="pln">addToClass</span><span class="pun">(</span><span class="pln">student</span><span class="pun">.</span><span class="pln">createStudent</span><span class="pun">(</span><span class="str">"Rose"</span><span class="pun">,</span><span class="pln"> </span><span class="str">"female"</span><span class="pun">));</span><span class="pln"> console</span><span class="pun">.</span><span class="pln">log</span><span class="pun">(</span><span class="pln">clz</span><span class="pun">.</span><span class="pln">getClassSize</span><span class="pun">());</span><span class="pln"> </span><span class="com">// 输出 2 </span><span class="pln"> </span><span class="pun">});</span></code>
student 模块和 class 模块都是独立的模块,下面我们再定义一个新的模块,这个模块依赖 student 和 class 模块,这样主程序部分的逻辑也可以包装进去了。
案例七: 依赖 student 和 class 模块的 manager 模块,manager.js
<code><span class="pln"> define</span><span class="pun">([</span><span class="str">"js/student"</span><span class="pun">,</span><span class="pln"> </span><span class="str">"js/class"</span><span class="pun">],</span><span class="pln"> </span><span class="kwd">function</span><span class="pun">(</span><span class="pln">student</span><span class="pun">,</span><span class="pln"> clz</span><span class="pun">){</span><span class="pln"> </span><span class="kwd">return</span><span class="pln"> </span><span class="pun">{</span><span class="pln"> addNewStudent</span><span class="pun">:</span><span class="pln"> </span><span class="kwd">function</span><span class="pun">(</span><span class="pln">name</span><span class="pun">,</span><span class="pln"> gender</span><span class="pun">){</span><span class="pln"> clz</span><span class="pun">.</span><span class="pln">addToClass</span><span class="pun">(</span><span class="pln">student</span><span class="pun">.</span><span class="pln">createStudent</span><span class="pun">(</span><span class="pln">name</span><span class="pun">,</span><span class="pln"> gender</span><span class="pun">));</span><span class="pln"> </span><span class="pun">},</span><span class="pln"> getMyClassSize</span><span class="pun">:</span><span class="pln"> </span><span class="kwd">function</span><span class="pun">(){</span><span class="pln"> </span><span class="kwd">return</span><span class="pln"> clz</span><span class="pun">.</span><span class="pln">getClassSize</span><span class="pun">();</span><span class="pln"> </span><span class="pun">}</span><span class="pln"> </span><span class="pun">};</span><span class="pln"> </span><span class="pun">});</span></code>
案例八:新的主程序
<code><span class="pln"> </span><span class="kwd">require</span><span class="pun">([</span><span class="str">"js/manager"</span><span class="pun">],</span><span class="pln"> </span><span class="kwd">function</span><span class="pun">(</span><span class="pln">manager</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln"> manager</span><span class="pun">.</span><span class="pln">addNewStudent</span><span class="pun">(</span><span class="str">"Jack"</span><span class="pun">,</span><span class="pln"> </span><span class="str">"male"</span><span class="pun">);</span><span class="pln"> manager</span><span class="pun">.</span><span class="pln">addNewStudent</span><span class="pun">(</span><span class="str">"Rose"</span><span class="pun">,</span><span class="pln"> </span><span class="str">"female"</span><span class="pun">);</span><span class="pln"> console</span><span class="pun">.</span><span class="pln">log</span><span class="pun">(</span><span class="pln">manager</span><span class="pun">.</span><span class="pln">getMyClassSize</span><span class="pun">());</span><span class="com">// 输出 2 </span><span class="pln"> </span><span class="pun">});</span></code>
通过上面的代码示例,我们已经清楚的了解了如何写一个模块,这个模块如何被使用,模块间的依赖关系如何定义。还是有一些使用技巧需要提示一下:
尽量不要提供模块的 ID,如 AMD 规范所述,这个 ID 是可选项,如果提供了,在 RequireJS 的实现中会影响模块的可迁移性,文件位置变化会导致需要手动修改该 ID。
每个 JavaScript 文件只定义一个模块,模块名称和文件路径的查找算法决定了这种方式是最优的,多个的模块和文件会被优化器进行优化。
避免模块的循环依赖,如果实在避免不了,可以模块中加上对”require”模块的依赖,在代码中直接用
<code><span class="kwd">require</span><span class="pun">(”</span><span class="pln">dependencyModuleName</span><span class="pun">”)</span></code>
配置 RequireJS
前面的介绍中,我们似乎忽略了一个基本问题,模块名字是怎么来的?当我在 require 一个模块时,这个模块是如何映射到具体的 JavaScript 文件上去?这就涉及到如何配置 RequireJS。
最简化的加载 RequireJS 的方式如案例2 所示,在这种情况下,我们没有指定一个 baseUrl 和 paths 给
RequireJS,如果通过如案例10 所示方式,则 data-main 指定了一个在当前 index.html 目录并行的文件夹下的
/js/main.js 作为程序入口,而 /js 目录也将作为 baseUrl 在其他模块定义时候使用。
案例九:载入 require.js
<code><span class="pln"> </span><span class="tag"><script</span><span class="pln"> </span><span class="atn">data-main</span><span class="pun">=</span><span class="atv">"js/main"</span><span class="pln"> </span><span class="atn">src</span><span class="pun">=</span><span class="atv">"scripts/require.js"</span><span class="tag">></script></span></code>
因此,我们前面示例中的所有模块依赖,都可以去掉”js/”,直接写 ”student”, ”class”,”manager” 等。
一种更为直接的方式显示指定 baseUrl 和 paths 就是利用 require.config 来设置这些参数。如案例十 所示。
案例十. 配置 RequireJS
<code><span class="pln"> </span><span class="tag"><script</span><span class="pln"> </span><span class="atn">type</span><span class="pun">=</span><span class="atv">"text/javascript"</span><span class="pln"> </span><span class="atn">src</span><span class="pun">=</span><span class="atv">"./js/require.js"</span><span class="tag">></script></span><span class="pln"> </span><span class="tag"><script</span><span class="pln"> </span><span class="atn">type</span><span class="pun">=</span><span class="atv">"text/javascript"</span><span class="tag">></span><span class="pln"> require</span><span class="pun">.</span><span class="pln">config</span><span class="pun">({</span><span class="pln"> baseUrl</span><span class="pun">:</span><span class="pln"> </span><span class="str">"./js"</span><span class="pun">,</span><span class="pln"> paths</span><span class="pun">:</span><span class="pln"> </span><span class="pun">{</span><span class="pln"> </span><span class="str">"some"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"some/v1"</span><span class="pln"> </span><span class="pun">},</span><span class="pln"> waitSeconds</span><span class="pun">:</span><span class="pln"> </span><span class="lit">10</span><span class="pln"> </span><span class="pun">});</span><span class="pln"> require</span><span class="pun">(</span><span class="pln"> </span><span class="pun">[</span><span class="str">"some/module"</span><span class="pun">,</span><span class="pln"> </span><span class="str">"my/module"</span><span class="pun">,</span><span class="pln"> </span><span class="str">"./js/a.js"</span><span class="pun">],</span><span class="pln"> </span><span class="kwd">function</span><span class="pun">(</span><span class="pln">someModule</span><span class="pun">,</span><span class="pln"> myModule</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{}</span><span class="pln"> </span><span class="pun">);</span><span class="pln"> </span><span class="tag"></script></span></code>
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
当 RequireJS 与其他框架一起工作的时候,显然它是可以作为统一的加载器来加载其他框架。鉴于 jQuery、Dojo 等已经支持了 AMD ,那么就有可能在定义自己的模块的时候,通过适当配置,直接把其他框架的模块作为依赖对象。
RequireJS 和 Dojo
案例十是一个基本的 RequireJS 和 Dojo 集成配置方法,这里关键是将 dojo 和 dijit 都在 packages 里面注册一下。
案例十一:配置 RequireJS 和 Dojo
<code><span class="pln"> </span><span class="tag"><script></span><span class="pln"> require </span><span class="pun">=</span><span class="pln"> </span><span class="pun">{</span><span class="pln"> packages</span><span class="pun">:</span><span class="pln"> </span><span class="pun">[</span><span class="pln"> </span><span class="pun">{</span><span class="pln"> name</span><span class="pun">:</span><span class="pln"> </span><span class="pun">“</span><span class="pln">dojo</span><span class="pun">”,</span><span class="pln"> location</span><span class="pun">:</span><span class="pln"> </span><span class="pun">“</span><span class="pln">dojo</span><span class="pun">”,</span><span class="pln"> main</span><span class="pun">:”</span><span class="pln">lib</span><span class="pun">/</span><span class="pln">main</span><span class="pun">-</span><span class="pln">browser</span><span class="pun">”,</span><span class="pln"> lib</span><span class="pun">:</span><span class="pln"> </span><span class="pun">“.”</span><span class="pln"> </span><span class="pun">},</span><span class="pln"> </span><span class="pun">{</span><span class="pln"> name</span><span class="pun">:</span><span class="pln"> </span><span class="pun">“</span><span class="pln">dijit</span><span class="pun">”,</span><span class="pln"> location</span><span class="pun">:</span><span class="pln"> </span><span class="pun">“</span><span class="pln">dijit</span><span class="pun">”,</span><span class="pln"> main</span><span class="pun">:”</span><span class="pln">lib</span><span class="pun">/</span><span class="pln">main</span><span class="pun">”,</span><span class="pln"> lib</span><span class="pun">:</span><span class="pln"> </span><span class="pun">“.”</span><span class="pln"> </span><span class="pun">}</span><span class="pln"> </span><span class="pun">],</span><span class="pln"> paths</span><span class="pun">:</span><span class="pln"> </span><span class="pun">{</span><span class="pln"> require</span><span class="pun">:</span><span class="pln"> </span><span class="pun">“./</span><span class="pln">js</span><span class="pun">”</span><span class="pln"> </span><span class="pun">},</span><span class="pln"> ready</span><span class="pun">:</span><span class="pln"> </span><span class="kwd">function</span><span class="pln"> </span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln"> require</span><span class="pun">([“</span><span class="pln">my</span><span class="pun">/</span><span class="pln">module</span><span class="pun">”],</span><span class="pln"> </span><span class="kwd">function</span><span class="pln"> </span><span class="pun">(</span><span class="pln">module</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln"> </span><span class="pun">});</span><span class="pln"> </span><span class="pun">}</span><span class="pln"> </span><span class="pun">};</span><span class="pln"> </span><span class="tag"></script></span></code>
在定义 module 这个模块时就可以直接将 dojo 和 dijit 里的模块作为依赖对象了。
RequireJS 和 jQuery
把 jQuery 作为一个依赖模块来使用也非常简单,只需要在 RequireJS 里的 config 里做相应的配置就可以了。
案例十二:配置 RequireJS 和 jQuery
<code><span class="pln"> </span><span class="kwd">require</span><span class="pun">.</span><span class="pln">config</span><span class="pun">({</span><span class="pln"> paths</span><span class="pun">:</span><span class="pln"> </span><span class="pun">{</span><span class="pln"> </span><span class="str">"jquery"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"./js/jquery-1.7"</span><span class="pln"> </span><span class="pun">}</span><span class="pln"> </span><span class="pun">});</span><span class="pln"> </span><span class="kwd">require</span><span class="pun">([</span><span class="str">"jquery"</span><span class="pun">],</span><span class="kwd">function</span><span class="pun">(</span><span class="pln">jQ</span><span class="pun">){</span><span class="pln"> console</span><span class="pun">.</span><span class="pln">log</span><span class="pun">(</span><span class="pln">jQ</span><span class="pun">);</span><span class="pln"> </span><span class="pun">});</span></code>
RequireJS 和 Web Workers
Web Workers 是多线程的 JavaScript,每个 worker 里面的脚本都 会启动一个新的线程来执行,通过在 worker
里面用 importsScript 来加载 require.js 可以直接在 worker 里面使用 RequireJS 。
案例十三: RequireJS 和 Web Workers
<code><span class="pln"> importScripts</span><span class="pun">(</span><span class="str">’./js/require.js’</span><span class="pun">);</span><span class="pln"> </span><span class="kwd">require</span><span class="pun">([</span><span class="str">"require"</span><span class="pun">],</span><span class="pln"> </span><span class="kwd">function</span><span class="pun">(</span><span class="kwd">require</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln"> postMessage</span><span class="pun">(</span><span class="str">"test"</span><span class="pun">);</span><span class="pln"> </span><span class="pun">}</span><span class="pln"> </span><span class="pun">);</span></code>