Javascript文件加载:LABjs和RequireJS

作者: 阮一峰

日期: 2011年10月 3日

珠峰培训

传统上,加载Javascript文件都是使用<script>标签。

就像下面这样:

  <script type="text/javascript" src="example.js"></script>

<script>标签很方便,只要加入网页,浏览器就会读取并运行。但是,它存在一些严重的缺陷。

  (1)严格的读取顺序。由于浏览器按照<script>在网页中出现的顺序,读取Javascript文件,然后立即运行,导致在多个文件互相依赖的情况下,依赖性最小的文件必须放在最前面,依赖性最大的文件必须放在最后面,否则代码会报错。

  (2)性能问题。浏览器采用"同步模式"加载<script>标签,也就是说,页面会"堵塞"(blocking),等待javascript文件加载完成,然后再运行后面的HTML代码。当存在多个<script>标签时,浏览器无法同时读取,必须读取完一个再去读取另一个,造成读取时间大大延长,页面响应缓慢。

为了解决这些问题,可以使用DOM方法,动态加载Javascript文件。

  function loadScript(url){

    var script = document.createElement("script");

    script.type = "text/javascript";

    script.src = url;

    document.body.appendChild(script);

  }

这样做的原理是,浏览器即时创造出一个<script>标签,然后"异步"读取Javascript文件。这样不会造成页面堵塞,但会造成另外一个问题:这样加载的Javascript文件,不在原始的DOM结构之中,因此在DOM-ready(DOMContentLoaded)事件和window.onload事件中指定的回调函数对它无效。

外部函数库LABjsRequireJS,可以帮助我们更有效地管理Javascript加载。

下面根据ScriptJunkie的文章,举一个最简单的例子,来说明这两个函数库的基本用法。更高级的用法,请参阅它们的文档。

  <script src="script1.js"></script>

  <script src="script2-a.js"></script>

  <script src="script2-b.js"></script>

  <script type="text/javascript">

    initScript1();

    initScript2();

  </script>

  <script src="script3.js"></script>

  <script type="text/javascript">

    initScript3();

  </script>

上面这段代码,将依次加载4个javascript文件:script1.js、script2-a.js、script2-b.js和script3.js。在加载完前三个文件后,运行两个函数initScript1()和initScript2();加载完第四个文件后,再运行函数initScript3()。

下面,用LABjs对其进行改写:

  <script src="LAB.js"></script>

  <script type="text/javascript">

    $LAB

     .script("script1.js").wait()

     .script("script2-a.js")

     .script("script2-b.js")

     .wait(function(){

       initScript1();

       initScript2();

     })

     .script("script3.js")

     .wait(function(){

       initScript3();

     });

  </script>

首先,$LAB对象替代了<script>标签,然后.script()方法表示加载Javascript文件,不带参数的.wait()方法表示立即运行刚才加载的Javascript文件,带参数的.wait()方法也是立即运行刚才加载的Javascript文件,但是还运行参数中指定的函数。

这里需要注意的是,可以同时运行多条$LAB链,但是它们之间是完全独立的,不存在次序关系。如果你要确保一个Javascript文件在另一个文件之后运行,你只能把它们写在同一个链操作之中。只有当某些脚本是完全无关的时候,你才应该考虑把它们分成不同的$LAB链,表示它们之间不存在相关关系。

接下来是requireJS的改写:

  <script src="require.js"></script>

  <script type="text/javascript">

    require([

      "script1.js",
      "script2-a.js",
      "script2-b.js",
      "script3.js"

     ],

     function(){

      initScript1();
      initScript2();
      initScript3();

     }

    );

  </script>

require()接受两个参数,第一个数组表示所要加载的Javascript文件,第二个是加载完成后所要运行的回调函数。原生的require()不支持按次序加载,所以四个Javascript文件到底先加载哪个,无法事前知道,require()只保证这四个文件全部加载完成之后,才会运行所指定的回调函数。

如果按次序加载对你很重要,你可以使用官方提供的order插件

(完)

一灯学堂

优达学城

留言(22条)

这篇文章不够严谨,浏览器发展很快,默认行为已经优化很多。比如在 Chrome 和 Firefox 等最新版本中,多个 script 会并行下载,并且在下载 script 时,还会根据情况,启用另一个 thread 提前下载后续页面资源。

模块加载器,推荐国内前端达人玉伯的 seajs: http://seajs.com/ 是我用过的最简洁优雅的模块定义和加载框架,比 requirejs 好用,可惜的是好像只在国内有一定市场,国外尚未推广开。

这篇确实是技术帖,看不懂,但还是感謝阮兄的辛苦劳动。

对呢,玉伯的seaJS非常好用!

楼主怎么好长时间只写技术类文章了?写点别的题材我们也爱看的。谢谢!

引用haz的发言:

这篇文章不够严谨,浏览器发展很快,默认行为已经优化很多。


确实,这篇文章不太严谨的,加载顺序说的也不太对的...

文中举的例子, 应该是按序加载吧, 即先加载并执行 script1.js、script2-a.js、script2-b.js, 然后执行 initScript1()和initScript2(), 接着再加载并执行 script3.js, 最后执行 initScript3().

引用haz的发言:

这篇文章不够严谨,浏览器发展很快,默认行为已经优化很多。比如在 Chrome 和 Firefox 等最新版本中,多个 script 会并行下载,并且在下载 script 时,还会根据情况,启用另一个 thread 提前下载后续页面资源。

此外, script 标签还可以加 defer 属性,使得这个标签不需要以阻塞形式进行解释和执行,随时下载完毕随时执行。

其实现在 loader 的技术已经很成熟,区别只是在于 preference 而已,有些人觉得这样写更好,有些人觉得那样写更好。

我曾经做过实验,也写过类似的一篇分析js资源加载问题文章。FireFox中脚本是可以并行加载的,相信大部分现代浏览器都应该对此做过优化设计。

动态加载能够大幅缩短页面domready的时间,但是页面最终载入时间的优势不是非常大,但是requireJS和seaJS这种其实更重要的是实现了commonJS这套标准,将js以模块化的形式来实现,有利于构建JS的通用库。

没错,现代浏览器已不存在js顺序加载的问题了,
但很多网站要求必须支持ie6,这个就必须的了!

SeaJS 规定了一套固定的模块模式,使用的话需要改变编码风格。

恩,正好被这个问题困扰,谢谢分享,先回帖后看

引用windyrobin的发言:

确实,这篇文章不太严谨的,加载顺序说的也不太对的...

这篇文章的重点不是 JavaScript 加载顺序问题,至于 defer,应该是 html5 中的新属性。RequireJS 能很好地管理外部 JS 文件而已。

第一次来这,觉得博主涉猎真广

本来想找一此amd的内容来看的,结果发现各种实现好乱啊,小程序还不如自己手动写script标签实现来得简单。

seajs是不错,其CMD规范与commonJS长得更像,但是还远未i18n,目前最吸引人眼球的typescript采用了AMD,所有在纠结之后还是站到requireJS的队列里了。

“这样加载的Javascript文件,不在原始的DOM结构之中,因此在DOM-ready(DOMContentLoaded)事件和window.onload事件中指定的回调函数对它无效。”
这句话是什么意思?能给个例子吗?

文中提到采用动态加载的脚本会遇到的问题:
“这样加载的Javascript文件,不在原始的DOM结构之中,因此在DOM-ready(DOMContentLoaded)事件和window.onload事件中指定的回调函数对它无效。”
是指window.onload和document.DOMContentLoaded无法fire动态加载的脚本的函数吗?
但是我测试了如下代码发现onload是可以fire动态加载脚本的函数的:
----html----
<body>
<p>onload will fire the dydamic script?</p>
<script src="loadScript.js"></script>
<script>
//dynamicScript.js 拥有一个fire函数
loadScript('dynamicScript.js', function () {
console.log('Script loaded.');
window.onload = fire;
});
</script>
</body>

---- dynamicScript.js----
function fire() {
alert('Yes!');
}

是我对这句话的理解有误吗?
菜鸟一只,望谅解!

To differui,

(1)粗略的讲,浏览器遇到<script>标签时就会把Parser停下来,解析标签内容,然后继续向前解析后面的HTML文档内容。你的例子的<script>在<body>中,所以“window.onload=...”会在</body>之前被执行。

(2)你如果在消息响应函数中加载JS代码(比如input的onclick中用XHR加载),“window.onload“就无法调用你后加载的JS。因为你有机会点按钮时,onload事件已经执行完了。

我一直在作JS引擎的集成(偶尔贡献代码)工作,最近想关注一下上层应用的东西,这篇文章正是我想看的,谢谢作者。

只可惜不支持http://xxx.com/jQuery.js格式。。
只支持本域

引用VAllen的发言:

只可惜不支持http://xxx.com/jQuery.js格式。。
只支持本域

EXAMPLE LOADING JQUERY FROM A CDN
§ 5
This is an example on how to load an optimize your code while loading jQuery from a Content Delivery Network (CDN). This example requires all your jQuery plugins to call define() to properly express their dependencies. Shim config does not work after optimization builds with CDN resources.

2年前的文章,依然有参考价值!精品文章!

根据我的测试,window.onload是在loadScript把js加入dom后才触发的。

我要发表看法

«-必填

«-必填,不公开

«-我信任你,不会填写广告链接