解決「阻塞效應」-解決腳本文件下載阻塞網頁渲染的問題
- 2019 年 10 月 3 日
- 筆記
defer 屬性
為了解決腳本文件下載阻塞網頁渲染的問題,一個方法是對<script>
元素加入defer
屬性。它的作用是延遲腳本的執行,等到 DOM 載入生成後,再執行腳本。
<script src="a.js" defer></script> <script src="b.js" defer></script>
上面程式碼中,只有等到 DOM 載入完成後,才會執行a.js
和b.js
。
defer
屬性的運行流程如下。
- 瀏覽器開始解析 HTML 網頁。
- 解析過程中,發現帶有
defer
屬性的<script>
元素。 - 瀏覽器繼續往下解析 HTML 網頁,同時並行下載
<script>
元素載入的外部腳本。 - 瀏覽器完成解析 HTML 網頁,此時再回過頭執行已經下載完成的腳本。
有了defer
屬性,瀏覽器下載腳本文件的時候,不會阻塞頁面渲染。下載的腳本文件在DOMContentLoaded
事件觸發前執行(即剛剛讀取完</html>
標籤),而且可以保證執行順序就是它們在頁面上出現的順序。
對於內置而不是載入外部腳本的script
標籤,以及動態生成的script
標籤,defer
屬性不起作用。另外,使用defer
載入的外部腳本不應該使用document.write
方法。
async 屬性
解決“阻塞效應”的另一個方法是對<script>
元素加入async
屬性。
<script src="a.js" async></script> <script src="b.js" async></script>
async
屬性的作用是,使用另一個進程下載腳本,下載時不會阻塞渲染。
- 瀏覽器開始解析 HTML 網頁。
- 解析過程中,發現帶有
async
屬性的script
標籤。 - 瀏覽器繼續往下解析 HTML 網頁,同時並行下載
<script>
標籤中的外部腳本。 - 腳本下載完成,瀏覽器暫停解析 HTML 網頁,開始執行下載的腳本。
- 腳本執行完畢,瀏覽器恢復解析 HTML 網頁。
async
屬性可以保證腳本下載的同時,瀏覽器繼續渲染。需要注意的是,一旦採用這個屬性,就無法保證腳本的執行順序。哪個腳本先下載結束,就先執行那個腳本。另外,使用async
屬性的腳本文件裡面的程式碼,不應該使用document.write
方法。
defer
屬性和async
屬性到底應該使用哪一個?
一般來說,如果腳本之間沒有依賴關係,就使用async
屬性,如果腳本之間有依賴關係,就使用defer
屬性。如果同時使用async
和defer
屬性,後者不起作用,瀏覽器行為由async
屬性決定。
腳本的動態載入
<script>
元素還可以動態生成,生成後再插入頁面,從而實現腳本的動態載入。
['a.js', 'b.js'].forEach(function(src) { var script = document.createElement('script'); script.src = src; document.head.appendChild(script); });
這種方法的好處是,動態生成的script
標籤不會阻塞頁面渲染,也就不會造成瀏覽器假死。但是問題在於,這種方法無法保證腳本的執行順序,哪個腳本文件先下載完成,就先執行哪個。
如果想避免這個問題,可以設置async屬性為false
。
['a.js', 'b.js'].forEach(function(src) { var script = document.createElement('script'); script.src = src; script.async = false; document.head.appendChild(script); });
上面的程式碼不會阻塞頁面渲染,而且可以保證b.js
在a.js
後面執行。不過需要注意的是,在這段程式碼後面載入的腳本文件,會因此都等待b.js
執行完成後再執行。
如果想為動態載入的腳本指定回調函數,可以使用下面的寫法。
function loadScript(src, done) { var js = document.createElement('script'); js.src = src; js.onload = function() { done(); }; js.onerror = function() { done(new Error('Failed to load script ' + src)); }; document.head.appendChild(js); }
參考文件:https://wangdoc.com/javascript/bom/engine.html