一個網頁的有不少地方能夠進行性能優化,比較常見的一種方式就是異步加載js腳本文件。在談異步加載以前,先來看看瀏覽器加載js文件的原理。javascript
瀏覽器加載 JavaScript 腳本,主要經過
<script>
元素完成。正常的網頁加載流程是這樣的。css
- 瀏覽器一邊下載 HTML 網頁,一邊開始解析。也就是說,不等到下載完,就開始解析。
- 解析過程當中,瀏覽器發現
<script>
元素,就暫停解析,把網頁渲染的控制權轉交給 JavaScript 引擎。- 若是
<script>
元素引用了外部腳本,就下載該腳本再執行,不然就直接執行代碼。- JavaScript 引擎執行完畢,控制權交還渲染引擎,恢復解析 HTML 網頁。
加載外部腳本時,瀏覽器會暫停頁面渲染,等待腳本下載並執行完成後,再繼續渲染。緣由是 JavaScript 代碼能夠修改 DOM,因此必須把控制權讓給它,不然會致使複雜的線程競賽的問題。java
上面所說的,就是咱們平時最多見到的,將`<script>`
標籤放到`<head>`
中的作法,這樣的加載方式叫作同步加載,或者叫阻塞加載,由於在加載js腳本文件時,會阻塞瀏覽器解析HTML文檔,等到下載並執行完畢以後,纔會接着解析HTML文檔。若是加載時間過長(好比下載時間太長),就會形成瀏覽器「假死」,頁面一片空白。並且,放在`<head>`
中同步加載的js文件中不能對DOM進行操做,不然會產生錯誤,由於這個時候HTML尚未進行解析,DOM尚未生成。由此看來,同步加載帶來的體驗每每並很差。jquery
下面咱們來看幾種異步加載的方式。瀏覽器
<script>
標籤放到<body>
底部嚴格來講,這並不算是異步加載,可是這也是常見的經過改變js加載方式來提高頁面性能的一種方式,因此也就放到這裏來講。
將<script>
放到<body>
底部,解決上上面說到的幾個問題,一是不會形成頁面解析的阻塞,就算加載時間過長用戶也能夠看到頁面而不是一片空白,並且這時候能夠在腳本中操做DOM。性能優化
defer
屬性經過給<script>
標籤設置defer
屬性,將腳本文件設置爲延遲加載,當瀏覽器遇到帶有defer
屬性的<script>
標籤時,會再開啓一個線程去下載js文件,同時繼續解析HTML文檔,等等HTML所有解析完畢DOM加載完成以後,再去執行加載好的js文件。
這種方式只適用於引用外部js文件的<script>
標籤,能夠保證多個js文件的執行順序就是它們在頁面中出現的順序,可是要注意,添加defer
屬性的js文件不該該使用document.write方法。網絡
async
屬性async
屬性和defer
屬性相似,也是會開啓一個線程去下載js文件,但和defer
不一樣的時,它會在下載完成後馬上執行,而不是會等到DOM加載完成以後再執行,因此仍是有可能會形成阻塞。
一樣的,async
也是隻適用於外部js文件,也不能在js中使用document.write方法,可是對多個帶有async
的js文件,它不能像defer那樣保證按順序執行,它是哪一個js文件先下載完就先執行哪一個。異步
<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.DOMContentLoaded
和window.onload
這兩個事件的區別,前者是在DOM解析完畢以後觸發,這時候DOM解析完畢,JavaScript能夠獲取到DOM引用,可是頁面中的一些資源好比圖片、視頻等尚未加載完,做用同jQuery中的ready事件。後者則是頁面徹底加載完畢,包括各類資源。
說完了這幾種常見的異步加載js腳本的方式,再來看最後一個問題,何時用defer
,何時用async
呢?通常來講,二者之間的選擇則是看腳本之間是否有依賴關係,有依賴的話應當要保證執行順序,應當使用defer
沒有依賴的話使用async
,同時使用的話defer
失效。要注意的是二者都不該該使用document.write,這個致使整個頁面被清除。
下面一幅圖代表了同步加載以及defer
、async
加載時的區別,其中綠色線表明 HTML 解析,藍色線表明網絡讀取js腳本,紅色線表明js腳本執行時間: