转:无阻塞加载脚本/示例

随着越来越多的站点向”Web2.0″应用发展,脚本的数量也在迅速增加。而与此同时脚本给页面性能带来的负面影响也是令人担忧的。在主流浏览器(例如IE6、IE7)中,由于脚本而产生的阻塞有以下两种:

  脚本会阻塞下载位于它之后的资源。

  脚本会阻塞渲染位于它之后的元素。

  Scripts Block Downloads example 示例演示了以上两点。在该示例的页面中包含两个外部脚本,紧随其后的是一个图片资源、一个样式表以及一个iframe。从在iE7中运行该示例时的 HTTP瀑布图中可以看出,第一个脚本阻塞了所有资源(包括第二个脚本)的下载,然后是第二个脚本阻塞了其后所有资源的下载,再往后可以看出图片、样式表 和iframe并行地完成了下载。当你仔细观察页面的渲染过程时,你会发现,位于脚本上方的文字段落在页面加载的一开始就完成了渲染。然而脚本之后的内容 的渲染过程在脚本下载完之前一直处于阻塞的状态。

  

 

  IE6&7, Firefox 2&3.0, Safari 3, Chrome 1, and Opera中的脚本阻塞

  由于浏览器是单线程的,因此脚本在执行的时候会阻塞下载其他资源,这一点很容易理解。但即便仅仅是在下载脚本,浏览器也无法下载其他资源就比较 令人费解了。这一现象在现代浏览中也存在,包括IE8、Safari4和Chrome2。在IE8中运行Scripts Block Downloads example 示例时的瀑布图显示脚本以及样式表确实已经可以并行下载了,但是其他的图片和iframe仍然会被阻塞。Safari4和Chrome2的情况也是类似 的。并行下载有所改善,但仍然有待改进。

  

 

  即便是在 IE8, Safari 4, 和 Chrome 2中,脚本仍然会阻塞资源下载

  幸运的是,目前有很多方式可以实现脚本的无阻塞加载,即便是在旧浏览器中也有效。不幸的是,这些方式都需要开发者付出额外的劳动。

  目前实现脚本的无阻塞加载主要有6种方式:

  XHR Eval - 通过XHR获取脚本并用Eval来执行responseText。

  XHR Injection - 通过XHR获取脚本,在页面中创建一个script元素并将它的text属性设置为responseText。

  Script in Iframe - 将需要的脚本放到一个页面中,然后通过iframe来加载该页面。

  Script DOM Element - 创建一个脚本元素并将它的src属性设置为脚本的URL。

  Script Defer - 给script标签添加defer属性。该方法原本仅适用于IE,但是目前对于Firefox3.1同样有效。

  document.write Script Tag - 通过document.write将<script src=”">写入页面。该方式仅在IE中能解决阻塞问题。

技术

 

并行下载

 

可以跨域

 

存在Script标签

 

忙碌指示

顺序保证

大小(bytes)

XHR Eval

IE, FF, Saf, Chr, Op

no

no

Saf, Chr

-

~500

XHR Injection

IE, FF, Saf, Chr, Op

no

yes

Saf, Chr

-

~500

Script in Iframe

IE, FF, Saf, Chr, Op

no

no

IE, FF, Saf, Chr

-

~50

Script DOM Element

IE, FF, Saf, Chr, Op

yes

yes

FF, Saf, Chr

FF, Op

~200

Script Defer

IE, Saf4, Chr2, FF3.1

yes

yes

IE, FF, Saf, Chr, Op

IE, FF, Saf, Chr, Op

~50

document.write Script Tag

IE, Saf4, Chr2, Op

yes

yes

IE, FF, Saf, Chr, Op

IE, FF, Saf, Chr, Op

~100

  现在的问题是:哪一种技术是最好的?这取决于你的应用场景:脚本是否和主页面同域;是否需要保证脚本的执行顺序;浏览器的忙碌指示是否应该被触发。

   在大多数情况下,Script DOM Element是一个好的选择。这种方式适用于所有的浏览器,而且没有跨域的限制,实现起来也非常的简单和易于理解。唯一的缺点是不能保证各个脚本的执行 顺序。如果需要加载多个有依赖关系的脚本,应该将这些脚本拼成一个来保证其按需要的顺序执行,或者使用别的技术。

  具体示例

  1. 原始页面(无忧化,直接链入外部script): http://varnow.org/pages/load-scripts-without-blocking.html

  时间瀑布图

  

blocking_timeline

 

  在无忧化的页面中,第一个脚本的下载和执行阻塞了其后的脚本下载以及页面的渲染,导致整个页面渲染完成需要4.8s。

  2. 使用XHR Eval加载脚本: http://varnow.org/pages/load-scripts-without-blocking-xhr-eval.html

  代码:

Ajax.get('/resources/js/lib/jquery.js',function(xhr){ 
    eval(xhr.responseText); 
}); 
Ajax.get('/resources/js/lib/ext-core-debug.js',function(xhr){ 
    eval(xhr.responseText); 
});

  时间瀑布图

  

without_blocking_xhr_eval_timeline2

 

  通过使用Ajax加载脚本来优化,页面整体的渲染时间和加载时间为3.5s左右,这主要依赖于解决了脚本加载阻塞的问题,从上图中可以看到两个脚本是并行加载的,而且未阻塞后面资源的加载。该方法还有一个优点在于不会触发IE和FF的资源加载状态条。

  该方法的缺点在于:

  (1)由于ajax请求无法跨域,因此加载的脚本必须与页面同域

  (2)不适用于加载多个有依赖关系的脚本,因为该方法无法保证脚本加载的顺序。

  3. 使用XHR获取脚本并创建script标签: http://varnow.org/pages/load-scripts-without-blocking-xhr-inject.html

  代码

Ajax.get('/resources/js/lib/jquery.js',function(xhr){ 
   injectScript(xhr.responseText); 
  }); 
  Ajax.get('/resources/js/lib/ext-core-debug.js',function(xhr){ 
   injectScript(xhr.responseText); 
  }); 
  function injectScript(scriptText){ 
   var s = document.createElement('script'); 
   s.text = scriptText; 
   document.getElementsByTagName('head')[0].appendChild(s); 
  } 

  时间瀑布图

  

without_blocking_xhr_inject_timeline1

 

  特点以及效果同方法2

  4. 将需要加载的脚本放入一个单独的页面并用iframe加载: http://varnow.org/pages/load-scripts-without-blocking-iframe.html

  时间瀑布图

  

without_blocking_iframe_timeline

 

  从上图可以看到,iframe的加载是在父页面DOM解析完成后开始的,而且iframe中的脚本加载对父页面未造成阻塞。使用该方法的一个不 好的方面是当加载未完成时,浏览器的状态条会一直显示为加载中;好处在于可以保证脚本加载的顺序是符合预期的,而且加载任何域下的脚本。

  5. 通过Dom操作创建script标签并设置src来加载脚本: http://varnow.org/pages/load-scripts-without-blocking-dom-script.html

  代码

function importScript(scriptUrls){ 
   for( var i =0,l=scriptUrls.length; i < l; i++){ 
    var s = document.createElement('script'); 
    s.src = scriptUrls[i]; 
    document.getElementsByTagName('head')[0].appendChild(s); 
   } 
  } 
  importScript(['/resources/js/lib/jquery.js','/resources/js/lib/ext-core-debug.js']);

  时间瀑布图

  

without_blocking_dom_script_timeline

 

  效果与使用iframe的方法基本相同,不过该方式实现起来更为简单,而且兼备跨域的功能,因此是目前使用较多的以一种方式。该方式在IE下不会导致浏览器进度条出现加载中的状态,在FF、Safari和Chrome下均会触发状态条

  6. 使用defer延迟加载: http://varnow.org/pages/load-scripts-without-blocking-defer.html

  代码

<script src="http://ajax.googleapis.com/ajax/libs/prototype/1.6.0.3/prototype.js></script
<script src=" http:="" ajax.googleapis.com="" ajax="" libs="" ext-core="" 3.0.0="" ext-core-debug.js"="" defer=""></script> 

  时间瀑布图

  

without_blocking_defer_timeline

 

  在该例子中,仅仅对第二个脚本即ext-core-debug.js使用了defer,原因接下来说明。从图中可以看出,没有使用defer进 行加载的prototype.js非常明显的阻塞了后续资源的加载,而使用了defer的ext-core-debug.js则没有阻塞效果。

  该方法的优点是可以跨域、无需额外脚本,实现起来非常简单。但缺点似乎更多:仅适用于IE、而且对于脚本有要求。所谓脚本有要求是指在脚本中不 能使用document.write,这会导致整个HTML页面被重置。这也正是不能给prototype.js使用defer的原因,下面是 prototype.js中的一段代码:

function() { 
  /* Support for the DOMContentLoaded event is based on work by Dan Webb,
     Matthias Miller, Dean Edwards and John Resig. */ 
var timer; 
 
function fireContentLoadedEvent() { 
    if (document.loaded) return; 
    if (timer) window.clearInterval(timer); 
    document.fire("dom:loaded"); 
    document.loaded = true; 
  } 
if (document.addEventListener) { 
    if (Prototype.Browser.WebKit) { 
      timer = window.setInterval(function() { 
        if (/loaded|complete/.test(document.readyState)) 
          fireContentLoadedEvent(); 
      }, 0); 
      Event.observe(window, "load", fireContentLoadedEvent); 
 
     } else { 
        document.addEventListener("DOMContentLoaded", 
        fireContentLoadedEvent, false); 
    } 
} else { 
    document.write("<script id=__onDOMContentLoaded defer src=//:><\/script>"); 
    $("__onDOMContentLoaded").onreadystatechange = function() { 
      if (this.readyState == "complete") { 
        this.onreadystatechange = null; 
        fireContentLoadedEvent(); 
      } 
    }; 
  } 
})();

  这段代码的作用是实现DOMContentLoaded事件,在IE下的实现方案是在页面中写入一个script并注册该script的 onreadystatechange来实现的。这里用到了document.write,如果在引入脚本的时候使用defer,那么在IE下会发现整个 页面都变成了空白。


转自:

http://www.php100.com/html/webkaifa/2011/0805/view_8536.html

posted @ 2011-11-02 15:13  caicainiao  阅读(360)  评论(0编辑  收藏  举报