RequireJs已经流行好久了,咱们在项目中也打算使用它。它提供了如下功能:javascript
初看起来并不复杂。php
在HTML中,添加这样的 <script>
标签:css
<script src="/path/to/require.js" data-main="/path/to/app/config.js"></script>
一般使用requirejs的话,咱们只须要导入requirejs便可,不须要显式导入其它的js库,由于这个工做会交给requirejs来作。html
属性 data-main
是告诉requirejs:你下载完之后,立刻去载入真正的入口文件。它通常用来对requirejs进行配置,而且载入真正的程序模块。java
config.js
中一般用来作两件事:jquery
requirejs.config({
baseUrl: '/public/js', paths: { app: 'app' } }); requirejs(['app'], function(app) { app.hello(); });
在 paths
中,咱们声明了一个名为 app
的模块,以及它对应的js文件地址。在最理想的状况下, app.js
的内容,应该使用requirejs的方式来定义模块:ruby
define([], function() { return { hello: function() { alert("hello, app~"); } } });
这里的 define
是requirejs提供的函数。requirejs一共提供了两个全局变量:app
另外还能够把 require
看成依赖的模块,而后调用它的方法:模块化
define(["require"], function(require) { var cssUrl = require.toUrl("./style.css"); });
前面的代码是理想的状况,即依赖的js文件,里面用了 define(...)
这样的方式来组织代码的。若是没用这种方式,会出现什么状况?函数
好比这个 hello.js
:
function hello() { alert("hello, world~"); }
它就按最普通的方式定义了一个函数,咱们能在requirejs里使用它吗?
先看下面不能正确工做的代码:
requirejs.config({
baseUrl: '/public/js', paths: { hello: 'hello' } }); requirejs(['hello'], function(hello) { hello(); });
这段代码会报错,提示:
Uncaught TypeError: undefined is not a function
缘由是最后调用 hello()
的时候,这个 hello
是个 undefined
. 这说明,虽然咱们依赖了一个js库(它会被载入),但requirejs没法从中拿到表明它的对象注入进来供咱们使用。
在这种状况下,咱们要使用 shim
,将某个依赖中的某个全局变量暴露给requirejs,看成这个模块自己的引用。
requirejs.config({
baseUrl: '/public/js', paths: { hello: 'hello' }, shim: { hello: { exports: 'hello' } } }); requirejs(['hello'], function(hello) { hello(); });
再运行就正常了。
上面代码 exports: 'hello'
中的 hello
,是咱们在 hello.js
中定义的hello
函数。当咱们使用 function hello() {}
的方式定义一个函数的时候,它就是全局可用的。若是咱们选择了把它 export
给requirejs,那当咱们的代码依赖于hello
模块的时候,就能够拿到这个 hello
函数的引用了。
因此: exports
能够把某个非requirejs方式的代码中的某一个全局变量暴露出去,看成该模块以引用。
但若是我要同时暴露多个全局变量呢?好比, hello.js
的定义实际上是这样的:
function hello() { alert("hello, world~"); } function hello2() { alert("hello, world, again~"); }
它定义了两个函数,而我两个都想要。
这时就不能再用 exports
了,必须换成 init
函数:
requirejs.config({
baseUrl: '/public/js', paths: { hello: 'hello' }, shim: { hello: { init: function() { return { hello: hello, hello2: hello2 } } } } }); requirejs(['hello'], function(hello) { hello.hello1(); hello.hello2(); });
当 exports
与 init
同时存在的时候, exports
将被忽略。
我遇到了一个折腾我很多时间的问题:为何我只能使用 jquery
来依赖jquery, 而不能用其它的名字?
好比下面这段代码:
requirejs.config({
baseUrl: '/public/js', paths: { myjquery: 'lib/jquery/jquery' } }); requirejs(['myjquery'], function(jq) { alert(jq); });
它会提示我:
jq is undefined
但我仅仅改个名字:
requirejs.config({
baseUrl: '/public/js', paths: { jquery: 'lib/jquery/jquery' } }); requirejs(['jquery'], function(jq) { alert(jq); });
就一切正常了,能打印出 jq
相应的对象了。
为何?我始终没搞清楚问题在哪儿。
常常研究,发现原来在jquery中已经定义了:
define('jquery', [], function() { ... });
它这里的 define
跟咱们前面看到的 app.js
不一样,在于它多了第一个参数'jquery'
,表示给当前这个模块起了名字 jquery
,它已是有主的了,只能属于jquery
.
因此当咱们使用另外一个名字:
myjquery: 'lib/jquery/jquery'
去引用这个库的时候,它会发现,在 jquery.js
里声明的模块名 jquery
与我本身使用的模块名 myjquery
不能,便不会把它赋给 myjquery
,因此 myjquery
的值是 undefined
。
因此咱们在使用一个第三方的时候,必定要注意它是否声明了一个肯定的模块名。
若是咱们不指明模块名,就像这样:
define([...], function() { ... });
那么它就是无主的模块。咱们能够在 requirejs.config
里,使用任意一个模块名来引用它。这样的话,就让咱们的命名很是自由,大部分的模块就是无主的。
能够看到,无主的模块使用起来很是自由,为何某些库(jquery, underscore)要把本身声明为有主的呢?
按某些说法,这么作是出于性能的考虑。由于像 jquery
, underscore
这样的基础库,常常被其它的库依赖。若是声明为无主的,那么其它的库极可能起不一样的模块名,这样当咱们使用它们时,就可能会屡次载入jquery/underscore。
而把它们声明为有主的,那么全部的模块只能使用同一个名字引用它们,这样系统就只会载入它们一次。
对于有主的模块,咱们还有一种方式能够挖墙角:不把它们看成知足requirejs规范的模块,而看成普通js库,而后在 shim
中导出它们定义的全局变量。
requirejs.config({
baseUrl: '/public/js', paths: { myjquery: 'lib/jquery/jquery' }, shim: { myjquery: { exports: 'jQuery' } } }); requirejs(['myjquery'], function(jq) { alert(jq); });
这样经过暴露 jQuery
这个全局变量给 myjquery
,咱们就能正常的使用它了。
不过咱们彻底没有必要这么挖墙角,由于对于咱们来讲,彷佛没有任何好处。
在前面引用jquery的这几种方式中,咱们虽然能够以模块的方式拿到jquery模块的引用,可是仍是能够在任何地方使用全局变量 jQuery
和 $
。有没有办法让jquery彻底不污染这两个变量?
首先尝试一种最简单可是不工做的方式:
requirejs.config({
baseUrl: '/public/js', paths: { jquery: 'lib/jquery/jquery' }, shim: { jquery: { init: function() { return jQuery.noConflict(true); } } } }); requirejs(['jquery'], function(jq) { alert($); });
这样是不工做的,仍是会弹出来一个非 undefined
的值。其缘由是,一旦requirejs为模块名 jquery
找到了属于它的模块,它就会忽略 shim
中相应的内容。也就是说,下面这段代码彻底没有执行:
jquery: { init: function() { return jQuery.noConflict(true); } }
若是咱们使用挖墙角的方式来使用jquery,以下:
requirejs.config({
baseUrl: '/public/js', paths: { myjquery: 'lib/jquery/jquery' }, shim: { myjquery: { init: function() { return jQuery.noConflict(true); } } } }); requirejs(['myjquery'], function(jq) { alert($); });
这样的确有效,这时弹出来的就是一个 undefined
。可是这样作的问题是,若是咱们引用的某个第三方库仍是使用 jquery
来引用jquery,那么就会报“找不到模块”的错了。
咱们要么得手动修改第三方模块的代码,要么再为它们提供一个 jquery
模块。可是使用后者的话,全局变量 $
可能又从新被污染了。
若是咱们有办法能让在继续使用 jquery
这个模块名的同时,有机会调用jQuery.noConflict(true)
就行了。
咱们能够再定义一个模块,仅仅为了执行这句代码:
jquery-private.js
define(['jquery'], function(jq) { return jQuery.noConflict(true); });
而后在入口处先调用它:
requirejs.config({
baseUrl: '/public/js', paths: { jquery: 'lib/jquery/jquery', 'jquery-private': 'jquery-private' } }); requirejs(['jquery-private', 'jquery'], function() { alert($); });
这样的确可行,可是仍是会有问题: 咱们必须当心的确保 jquery-private
永远是第一个被依赖,这样它才有机会尽早调用 jQuery.noConflict(true)
清除全局变量 $
和 jQuery
。这种保证只能靠人,很是不可靠。
咱们这时能够引入 map
配置,一劳永逸地解决这样问题:
requirejs.config({
baseUrl: '/public/js', paths: { jquery: 'lib/jquery/jquery', 'jquery-private': 'jquery-private' }, map: { '*': { 'jquery': 'jquery-private'}, 'jquery-private': { 'jquery': 'jquery'} } }); requirejs(['jquery'], function(jq) { alert($); });
这样作,就解决了前面的问题:在除了jquery-private以外的任何依赖中,还能够直接使用 jqurey
这个模块名,而且老是被替换为对 jquery-private
的依赖,使得它最早被执行。