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

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

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

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

加載外部腳本時,瀏覽器會暫停頁面渲染,等待腳本下載並執行完成後,再繼續渲染。緣由是 JavaScript 代碼能夠修改 DOM,因此必須把控制權讓給它,不然會致使複雜的線程競賽的問題。java

上面所說的,就是咱們平時最多見到的,將`<script>`標籤放到`<head>`中的作法,這樣的加載方式叫作同步加載,或者叫阻塞加載,由於在加載js腳本文件時,會阻塞瀏覽器解析HTML文檔,等到下載並執行完畢以後,纔會接着解析HTML文檔。若是加載時間過長(好比下載時間太長),就會形成瀏覽器「假死」,頁面一片空白。並且,放在`<head>`中同步加載的js文件中不能對DOM進行操做,不然會產生錯誤,由於這個時候HTML尚未進行解析,DOM尚未生成。由此看來,同步加載帶來的體驗每每並很差。jquery

下面咱們來看幾種異步加載的方式。瀏覽器

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文件,例以下面代碼:async

(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腳本執行時間:

相關文章
相關標籤/搜索