javascript脚本阻塞与模块化加载 #转载#

AndroidJava 2013-05-31

脚本位置和加载顺序:

       如果将脚本放在head内,那么再脚本执行完毕之前,显示给用户的始终是一片空白,用户只能傻傻的看着屏幕等待脚本执行完毕。
       而且,如果页面引入多个脚本,那么后面的脚本文件必须等待前面的脚本文件下载完毕并且执行完毕之后才能开始下载并运行。不过 IE8,FF,SAFARI,CHROME已经允许脚本文件可以同时下载,不过尽管如此,javascript脚本仍然会阻塞其他脚本下载进程,页面仍旧 要等待所有javascript脚本下载并执行完毕之后才可以开始加载渲染。
       因此,尽可能的将脚本文件放置在body标签的底部,以减少脚本阻塞对页面性能的影响,这也是Yahoo性能优化的第一条定律。
 
成组脚本加载:
       我们知道较少HTTP请求数可以有效提高页面加载速度,泽卡斯说:“下载一个100KB的JS文件要比下载4个25KB的JS文件速度快”。毕竟有请求和 响应的时间。所以我们可以将多个JS文件打包压缩成一个来提升性能。YUI通过CDN网络将客户端请求的多个JS文件在服务器端合并并压缩成一个返回给客 户端,从而提高加载效率。例如:
<script type="text/javascript" src="http://yui.yahooapis.com/combo?2.7.0/build/yahoo/yahoo-min.js&2.7.0/build/event/event-min.js "></script> 通过这个请求就可以把min.js和event-min.js两个文件在服务器端合并压缩成一个返回。
 
非阻塞脚本和延迟脚本(Deferred scripts):
       虽然将多个javascript脚本合并并且将脚本放在body底部降低了HTTP请求数并且部分解决了阻塞问题。但如果脚本文件很大, 而且在每个脚本中都有功能函数运行,那么在脚本文件加载时,会占用浏览器很长一段时间,这段时间用户也只能傻傻的看着屏幕玩弄着没有任何反应的浏览器。为 了避开这种情况,就出现了模块化加载和按需加载。
       HTML4为<script>标签扩展了defer属性,设置过该属性的script脚本可以放在页面的任何位置,并且不会阻塞页面其他资源 的下载,也就是说可以实现页面内容的并行加载。但下载完成后代码不会执行,只有等到DOM完全加载完毕后onload事件发生之前被执行。只有IE4和FF3.5的更高版本支持,不过在这篇文章中说webkit内核在HTML5也已经支持deffer和async。考虑到 浏览器兼容性和更加强大和灵活的脚本控制,我们就需要引入按需加载。
       我们可以通过动态创建script标记,更改其属性,并添加至head内,完成对script加载顺序、时间、依赖关系的控制。
var script  = document.createElement("script");
script.type ="text/javascript";
script.src="file1.js";
document.getElementsByTagName("head")[0].appendChild(script);
       这样做的好处在于:无论在什么地方加载file1.js,都不会阻塞和影响页面其他内容的加载,当然,会影响HTTP请求。一般情况下,通过动态节点下载 脚本文件时,file1.js被加载完毕之后,往往会立即执行(除了FF和Opera,他们会等待此前的所有动态节点脚本执行完毕)。当脚本文件是“自执 行(function(){})()”时,一切都很正常,但若只是一般函数命名定义或者只是提供了相关接口,就会有一些问题(至于什么问题,我没有验证, 但估计是脚本加载程度的问题,书中只说在这种情况下需要跟踪脚本下载完成并准备妥善的情况.若你有相关资料或者见解,非常希望能给出,并给予指点。)。针 对这一情况,FF,Chrome,Safari,Opera会在<script>节点脚本接收完成后发 出一个load事件,IE会给出readystatechanage事件(script元素有一个readyState属性,它的值随文件的下载状态而改 变,共有5中取值,不在一一列出,我们在这里使用loaded和complete来表示下载完成和所有数据已经准备好)。我们可以通过这两个事件判断脚本 是否加载完毕,下面是文中提供的一个封装好的函数,兼容各主流浏览器:
1:function loadScript(url, callback){
2:var script = document.createElement("script")
3:    script.type ="text/javascript";
4:if(script.readyState){//IE
5:          script.onreadystatechange = function(){
6:  if (script.readyState == "loaded" || script.readyState == "complete"){
7:                  script.onreadystatechange = null;
8:                  callback();
9:              }
10:          };
11:      } else {  //Others
12:          script.onload = function(){
13:              callback();
14:          };
15:      }
16:      script.src = url;
17:      document.getElementsByTagName("head")[0].appendChild(script);
18:  }
       两个参数@url:javascript文件URL、@callback:javascript接收完成时的回调函数,最后设置src属性, 将<script>元素添加至页面。这里为什么不是先设置src,然后才判断script加载情况呢?如果先设置了,还判断干毛?
       如果我们要按顺序和依赖关系加载多个javascript脚本文件,浏览器在此时并不保证文件执行顺序。所有浏览器中只有FF和Opera保证脚本按照下 载顺序执行,其他浏览器将按照服务器返回的顺序加载运行。那么我们就需要运用上面的例子在回调函数中按顺序加载执行脚本。
1:loadScript("file1.js",function(){
2:    loadScript("file2.js",function(){
3:        loadScript("file3.js",function(){
4:            alert("All files are loaded!");
5:});
6:});
7:});
       不过这样明显很麻烦,而且在速度上也是个问题。还有一种非阻塞脚本的方法是XHR脚本注入,也是异步加载,不会影响页面其他内容的加载进程。
推荐的脚本依赖和加载模式:
       在多个javascript脚本之间存在依赖关系时,必须将依赖性最小的放在最前面,将依赖性最大的放在最后面。若加上我们刚才的脚本阻塞和异步加载问题,下面给出可行性较高的解决方案:
       第一步:先向页面中加入“动态引入脚本(就像上面的loadScript)”的函数或库文件,因为这部分代码可能很少,所以下载运行非常迅速,不会对页面性能造成很大干扰。
       第二步:按需动态加载其他模块所需的脚本代码。
       例如:
1:<script type="text/javascript" src="loader.js"></script>
2:<script type="text/javascript">
3:    loadScript("the-rest.js",function(){
4:Application.init();
5:});
6:</script>
       记得将此代码放在</body>标记之前。这样不仅可以保证脚本不会影响页面其他内容,而且也不需要用额外的window.onload事件做判断。我们甚至可以将loader.js的内容直接放在页面中,这样可以减少一次http请求。

相关推荐