網頁性能優化之異步加載js文件


一個網頁的有很多地方可以進行性能優化,比較常見的一種方式就是異步加載js腳本文件。在談異步加載之前,先來看看瀏覽器加載js文件的原理。

瀏覽器加載 JavaScript 腳本,主要通過<script>元素完成。正常的網頁加載流程是這樣的。

  1. 瀏覽器一邊下載 HTML 網頁,一邊開始解析。也就是說,不等到下載完,就開始解析。
  2. 解析過程中,瀏覽器發現<script>元素,就暫停解析,把網頁渲染的控制權轉交給 JavaScript 引擎。
  3. 如果<script>元素引用了外部腳本,就下載該腳本再執行,否則就直接執行代碼。
  4. JavaScript 引擎執行完畢,控制權交還渲染引擎,恢復解析 HTML 網頁。

加載外部腳本時,瀏覽器會暫停頁面渲染,等待腳本下載並執行完成后,再繼續渲染。原因是 JavaScript 代碼可以修改 DOM,所以必須把控制權讓給它,否則會導致復雜的線程競賽的問題。

上面所說的,就是我們平時最常見到的,將`<script>`標簽放到`<head>`中的做法,這樣的加載方式叫做同步加載,或者叫阻塞加載,因為在加載js腳本文件時,會阻塞瀏覽器解析HTML文檔,等到下載並執行完畢之后,才會接着解析HTML文檔。如果加載時間過長(比如下載時間太長),就會造成瀏覽器“假死”,頁面一片空白。而且,放在`<head>`中同步加載的js文件中不能對DOM進行操作,否則會產生錯誤,因為這個時候HTML還沒有進行解析,DOM還沒有生成。由此看來,同步加載帶來的體驗往往並不好。

下面我們來看幾種異步加載的方式。

1. 將<script>標簽放到<body>底部

嚴格來說,這並不算是異步加載,但是這也是常見的通過改變js加載方式來提升頁面性能的一種方式,所以也就放到這里來說。
<script>放到<body>底部,解決上上面說到的幾個問題,一是不會造成頁面解析的阻塞,就算加載時間過長用戶也可以看到頁面而不是一片空白,而且這時候可以在腳本中操作DOM。

2. defer屬性

通過給<script>標簽設置defer屬性,將腳本文件設置為延遲加載,當瀏覽器遇到帶有defer屬性的<script>標簽時,會再開啟一個線程去下載js文件,同時繼續解析HTML文檔,等等HTML全部解析完畢DOM加載完成之后,再去執行加載好的js文件。
這種方式只適用於引用外部js文件的<script>標簽,可以保證多個js文件的執行順序就是它們在頁面中出現的順序,但是要注意,添加defer屬性的js文件不應該使用document.write方法。

3. async屬性

async屬性和defer屬性類似,也是會開啟一個線程去下載js文件,但和defer不同的時,它會在下載完成后立刻執行,而不是會等到DOM加載完成之后再執行,所以還是有可能會造成阻塞。
同樣的,async也是只適用於外部js文件,也不能在js中使用document.write方法,但是對多個帶有async的js文件,它不能像defer那樣保證按順序執行,它是哪個js文件先下載完就先執行哪個。

4. 動態創建<script>標簽

可以通過動態地創建<script>標簽來實現異步加載js文件,例如下面代碼:

(function(){
    var scriptEle = document.createElement("script");
    scriptEle.type = "text/javasctipt";
    scriptEle.async = true;
    scriptEle.src = "http://cdn.bootcss.com/jquery/3.0.0-beta1/jquery.min.js";
    var x = document.getElementsByTagName("head")[0];
    x.insertBefore(scriptEle, x.firstChild); 
})();

或者

(function(){
    if(window.attachEvent){
        window.attachEvent("load", asyncLoad);
    }else{
        window.addEventListener("load", asyncLoad);
    }
    var asyncLoad = function(){
    var ga = document.createElement('script'); 
    ga.type = 'text/javascript'; 
    ga.async = true; 
    ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js'; 
    var s = document.getElementsByTagName('script')[0]; 
    s.parentNode.insertBefore(ga, s);
    }
})();

上面兩種方法中,第一種方式執行完之前會阻止onload事件的觸發,而現在很多頁面的代碼都在onload時還執行額外的渲染工作,所以還是會阻塞部分頁面的初始化處理。第二種則不會阻止onload事件的觸發。
這里要簡要說明一下window.DOMContentLoadedwindow.onload這兩個事件的區別,前者是在DOM解析完畢之后觸發,這時候DOM解析完畢,JavaScript可以獲取到DOM引用,但是頁面中的一些資源比如圖片、視頻等還沒有加載完,作用同jQuery中的ready事件。后者則是頁面完全加載完畢,包括各種資源。

 

說完了這幾種常見的異步加載js腳本的方式,再來看最后一個問題,什么時候用defer,什么時候用async呢?一般來說,兩者之間的選擇則是看腳本之間是否有依賴關系,有依賴的話應當要保證執行順序,應當使用defer沒有依賴的話使用async,同時使用的話defer失效。要注意的是兩者都不應該使用document.write,這個導致整個頁面被清除。

下面一幅圖表明了同步加載以及deferasync加載時的區別,其中綠色線代表 HTML 解析,藍色線代表網絡讀取js腳本,紅色線代表js腳本執行時間:


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM