網(wǎng)站性能優(yōu)化(三)異步加載腳本

原則上來說,HTML在使用<script>標(biāo)簽加載外部腳本文件時(shí),會順序下載,順序執(zhí)行,并阻礙其他資源文件的下載,比如圖片(當(dāng)然,如今主流瀏覽器是可以實(shí)現(xiàn)JS和CSS文件并行下載)。

code1.png

(在Chrome下測試,三張圖片只會有兩張被阻塞。我猜測,Chrome是想做某些優(yōu)化的,但是,顯然優(yōu)化的不夠徹底。不同瀏覽器表現(xiàn)還是不一致的)


loading1.png

為了加速頁面渲染,不讓腳本文件阻塞其他資源下載,可以考慮“異步加載腳本”的技術(shù)。

后續(xù)的測試都基于Chrome瀏覽器(版本56.0.2924.87)。

1. Script DOM Element

這恐怕是最常見的異步加載腳本方法,即,動態(tài)創(chuàng)建一個(gè)script標(biāo)簽,并設(shè)置其src值。如下:

function createScript(url){
  var scrElem = document.createElement('script');
  srcElem.src = url;
  document.getElementsByTagName('head')[0].appendChild(scrElem);
}

createScript方式加載JS文件,不會阻塞下載其他資源。

loading2.png

但是這種方式會阻塞window.onload事件,參考chrome developer timeline:

timeline-script.png

優(yōu)點(diǎn)::

  • 支持跨域加載腳本文件
  • 兼容性最好、普適性最高的方案

缺點(diǎn)::

  • 腳本無序執(zhí)行
  • 會阻塞onload事件

2. XMLHttpRequest

通過XMLHttpRequest的方式下載腳本文件,然后使用eval或者動態(tài)添加<script>標(biāo)簽并設(shè)置其text屬性來執(zhí)行腳本。

// 不考慮IE
var xhrObj = new XMLHttpRequest();
xhrObj .onreadystatechange = function(){
  if (xhrObj .readyState == 4) {
    eval(xhrObj.responseText);  
    或者
    var scrElem = document.createElement('script');
    srcElem.text= xhrObj.responseText;
    document.getElementsByTagName('head')[0].appendChild(scrElem);
  }
}
xhrObj .open('GET', 'a.js', true);
xhrObj .send('');

稍微修改下上面的例子:

index.html

<html>
...
<body>
  <script type="application/javascript" src="js1.js"></script>
</body>
</html>

js1.js

var xhrObj = new XMLHttpRequest();
xhrObj .onreadystatechange = function(){
    if (xhrObj .readyState == 4) {
        var scrElem = document.createElement('script');
        srcElem.text= xhrObj.responseText;
        document.getElementsByTagName('head')[0].appendChild(scrElem);
    }
};
xhrObj .open('GET', 'js2.js', true);
xhrObj .send('');

查看chrome developer timeline:

timeline-xhr.png

優(yōu)點(diǎn)::

  • 將腳本下載和腳本執(zhí)行分離開,可以在適當(dāng)?shù)臅r(shí)候再執(zhí)行腳本。
  • 不會阻塞onload事件

缺點(diǎn)::

  • 通過XMLHttpRequest獲取的腳本文件必須和主頁面是同一個(gè)域名下。也就是說,不支持跨域下載腳本。因此不適合加載第三方文件。
  • 腳本無序執(zhí)行。

3. Script defer和async

兩者都支持異步加載文件,不同之處是,defer會在全部資源下載完畢后才執(zhí)行JS文件;async在腳本文件下載完就立刻執(zhí)行,并且,async模式加載的JS文件無法依序執(zhí)行,對于有順序依賴的腳本來說,不應(yīng)該采用這種方式。defer相對友好一些,并可以保證JS文件按照順序執(zhí)行。

稍微對程序做些修改:


code2.png
loading3.png

優(yōu)點(diǎn)::

  • defer和async優(yōu)點(diǎn):支持跨域加載腳本文件。
  • defer優(yōu)點(diǎn):可以保證JS文件按照順序執(zhí)行。

缺點(diǎn)::

  • defer和async缺點(diǎn):IE10以上(包括IE10)才支持。
  • async缺點(diǎn):JS文件無法依序執(zhí)行。
  • 會阻塞onload事件

4. Script in Iframe

創(chuàng)建了一個(gè)隱藏的iframe標(biāo)簽,設(shè)置其src值為JS代碼,然后插入到主頁面中。

這種方式在實(shí)際項(xiàng)目中很少用到,因?yàn)閕frame是開銷最高的DOM元素。常用場景是顯示廣告(廣告一般需要運(yùn)行在隔離環(huán)境中,iframe很合適)。

<iframe src=“a.html” frameborder="1" name="rightFrame" id="rightFrame"></iframe>

注意,src的值是a.html,而不是a.js,因?yàn)閕frame默認(rèn)其返回值為HTML文檔。所以需要在HTML文檔中把外部腳本轉(zhuǎn)成行內(nèi)腳本。

和XMLHttpRequest一樣,iframe不支持跨域加載腳本,且腳本無序執(zhí)行。

5. 小結(jié)

異步加載腳本還普遍存在另一個(gè)問題:無法保持多個(gè)腳本的執(zhí)行順序(除了defer)。
為了腳本依序執(zhí)行,可以采用如下方法:
1)定時(shí)器
利用setTimeoutsetInterval監(jiān)控第一個(gè)腳本執(zhí)行情況,一旦發(fā)現(xiàn)被執(zhí)行完,再繼續(xù)執(zhí)行下一個(gè)腳本。

  1. Script Onload
    利用script元素的onloadonreadystatechange事件處理程序,例子如下:
<script>
    var scrElem = document.createElement('script');
    scrElem.src = 'a.js';
    scrElem.onloadDone = false;
 
    scrElem.onload = function () {
        scrElem.onloadDone = true;
        // 執(zhí)行第二個(gè)腳本
    };
    //針對IE瀏覽器
    scrElem.onreadystatechange = function () {
        if ((scrElem.readyState === 'loaded' || scrElem.readyState === 'complete') && !scrElem.onloadDone) {
            scrElem.onloadDone = true;
            //執(zhí)行第二個(gè)腳本
        }
    };
    document.getElementsByTagName('head')[0].appendChild(scrElem);
</script>

注意:script.onload/onreadystatechange事件同樣會阻塞window.onload

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

推薦閱讀更多精彩內(nèi)容