瀏覽器環境下JavaScript腳本載入與執行探析之defer與async特性

来源:http://www.cnblogs.com/tracylin/archive/2016/01/14/5127636.html
-Advertisement-
Play Games

defer和async特性相信是很多JavaScript開發者"熟悉而又不熟悉"的兩個特性,從字面上來看,二者的功能很好理解,分別是"延遲腳本"和"非同步腳本"的作用。然而,以defer為例,一些細節問題可能開發者卻並不一定熟悉,比如:有了defer特性的腳本會延遲到什麼時候執行;內部腳本和外部腳本是...


defer和async特性相信是很多JavaScript開發者"熟悉而又不熟悉"的兩個特性,從字面上來看,二者的功能很好理解,分別是"延遲腳本"和"非同步腳本"的作用。然而,以defer為例,一些細節問題可能開發者卻並不一定熟悉,比如:有了defer特性的腳本會延遲到什麼時候執行;內部腳本和外部腳本是不是都能夠支持defer;defer後的腳本除了會延遲執行之外,還有哪些特殊的地方等等。本文結合已有的一些文章以及MDN文檔中對兩個特性的闡述,對defer和async進行更全面的研究和總結,希望能夠幫助開發者更好地掌握這兩個特性。

1 引言

在《瀏覽器環境下JavaScript腳本載入與執行探析之代碼執行順序》中我們提到過,JavaScript代碼的執行會阻塞頁面的解析渲染以及其他資源的下載,當然由於JavaScript是單線程語言,那就意味著在正常情況下,一個頁面中的JavaScript代碼只能按順序從上到下執行,當然,正如《瀏覽器環境下JavaScript腳本載入與執行探析之代碼執行順序》中我們分析的,在某些情況下,比如通過document.write進入腳本或者通過動態腳本技術引入腳本時,JavaScript代碼的執行順序不一定嚴格按照從上到下的順序,而defer和async也是我們所說的"非正常的情況"。

我們經常會說JavaScript的執行具有阻塞性,而在實際的開發中,我們通常最關心的阻塞,同時也是最影響用戶體驗的阻塞應該是以下幾個方面:

[1]頁面解析和渲染的阻塞

[2]我們寫的頁面初始化腳本(一般是監聽DOMContentLoaded事件所綁定的腳本,這部分腳本是我們希望最先執行的腳本,因為我們會把和用戶交互最相關的代碼寫在這裡)

[3]頁面外部資源下載的阻塞(比如圖片)

如果我們有一個耗時的腳本操作,而這段腳本又阻塞了上面我們提到的這三個地方,那麼這個網頁的性能或者用戶體驗就非常差了。

defer和async這兩個特性的初衷也是希望能夠解決或者緩解阻塞對於頁面體驗的影響,下麵我們就來分析一下這兩個特性,我們主要從以下幾個方面來全方位瞭解這兩個特性:

[1]延遲或非同步的腳本的執行時機是什麼時候?對於頁面的阻塞情況如何?

[2]內部腳本和外部腳本是否都能夠實現延遲或非同步?

[3]瀏覽器對這兩個特性的支持情況如何?有沒有相關的bug?

[4]使用了這兩個特性的腳本在使用時還有什麼需要註意的地方?

2 defer特性

2.1 關於defer腳本的執行時機

defer特性是HTML4規範中定義的擴展特性,最初只有IE4+和firefox3.5+才支持,之後chrome等瀏覽器也增加了對它的支持,使用的方式為defer="defer"。defer意為延遲,也就是會延遲腳本的執行。正常情況下,我們引入的腳本會被立即下載和執行,而有了defer特性之後,腳本下載完畢後不會立即執行,而是等到頁面解析完畢之後再執行。我們看一下HTML4標準對defer的闡述:

defer:When set, this boolean attribute provides a hint to the user agent that the script is not going to generate any document content (e.g., no "document.write" in javascript) and thus, the user agent can continue parsing and rendering.

也就是說,如果設置了defer,那麼就告訴用戶代理,這個腳本不會產生任何文檔內容,從而用戶代理可以繼續解析和渲染。我們再看一下MDN中對defer的關鍵描述:

defer:If the async attribute is not present but the defer attribute is present, then the script is executed when the page has finished parsing. 

通過標準中的定義,我們可以明確,即:defer的腳本不會阻塞頁面的解析,而是等到頁面解析結束之後再執行,但是耗時的defer依然可能會阻塞外部資源的下載,那麼它會阻塞DOMContentLoaded事件麽?事實上,defer的腳本依然是在DOMContentLoaded事件之前執行的,因此它還是會阻塞DOMContentLoaded中的腳本。我們可以通過下圖來幫助理解defer腳本的執行時機:

來源:https://html.spec.whatwg.org/multipage/scripting.html#attr-script-defer

根據標準中的定義,內部腳本不支持defer,而IE9及以下的瀏覽器則提供了內部腳本的defer支持

2.2 defer的瀏覽器支持情況

下麵我們來看一下defer特性的瀏覽器支持情況:

IE9及以下的瀏覽器存在一個bug,這個bug將在稍後的DEMO中進行詳細的說明。

2.3 DEMO:defer特性的功能驗證

我們模仿在Olivier Rochard在《the script defer attribute》使用的方式來驗證一下defer特性的功能:

首先我們準備了6個外部腳本:

1.js:

test += "我是head外部腳本\n";

 2.js

test += "我是body外部腳本\n";

 3.js

test += "我是底部外部腳本\n";

 defer1.js

test += "我是head外部延遲腳本\n";

 defer2.js

test += "我是body外部延遲腳本\n";

defer3.js

test += "我是底部外部延遲腳本\n";

 HTML中的代碼為:

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8"/>
    <title>defer attribute test</title>
    <script src="http://lib.sinaapp.com/js/jquery/1.9.1/jquery-1.9.1.min.js"></script>
    <script type="text/javascript">var test = "";</script>
    <script src="defer1.js" type="text/javascript" defer="defer"></script>
    <script src="1.js" type="text/javascript"></script>
    <script defer="defer">
        test += "我是head延遲內部腳本\n";
    </script>
    <script>
        test += "我是head內部腳本\n";
    </script>
</head>
<body>
<button id="test">點擊一下</button>
<script src="defer2.js" type="text/javascript" defer="defer"></script>
<script src="2.js" type="text/javascript"></script>
</body>
<script src="defer3.js" type="text/javascript" defer="defer"></script>
<script src="3.js" type="text/javascript"></script>
<script>
    $(function(){
        test += "我是DOMContentLoaded裡面的腳本\n";
    })
    window.onload = function(){
        test += "我是window.onload裡面的腳本\n";
        var button = document.getElementById("test");
        button.onclick = function(){
            alert(test);
        }
    }
</script>
</html>

 代碼中,為了方便實現DOMContentLoaded事件,我們引入了jQuery(之後的文章還會再介紹如何自己實現相容的DOMContentLoaded),然後,我們在腳本的head內、body內部和body外部分別引入延遲腳本和正常腳本,並且通過一個全局的字元串來記錄每一段代碼的執行狀態,我們看一下各個瀏覽器中的執行結果:

IE7 IE9 IE10 CHROME firefox

我是head外部腳本
我是head內部腳本
我是body外部腳本
我是底部外部腳本
我是head外部延遲腳本
我是head延遲內部腳本
我是body外部延遲腳本
我是底部外部延遲腳本
我是DOMContentLoaded裡面的腳本
我是window.onload裡面的腳本

 

我是head外部腳本
我是head內部腳本
我是body外部腳本
我是底部外部腳本
我是head外部延遲腳本
我是head延遲內部腳本
我是body外部延遲腳本
我是底部外部延遲腳本
我是DOMContentLoaded裡面的腳本
我是window.onload裡面的腳本

 

我是head外部腳本
我是head延遲內部腳本
我是head內部腳本
我是body外部腳本
我是底部外部腳本
我是head外部延遲腳本
我是body外部延遲腳本
我是底部外部延遲腳本
我是DOMContentLoaded裡面的腳本
我是window.onload裡面的腳本

我是head外部腳本
我是head延遲內部腳本
我是head內部腳本
我是body外部腳本
我是底部外部腳本
我是head外部延遲腳本
我是body外部延遲腳本
我是底部外部延遲腳本
我是DOMContentLoaded裡面的腳本
我是window.onload裡面的腳本


我是head外部腳本 我是head延遲內部腳本 我是head內部腳本 我是body外部腳本 我是底部外部腳本 我是head外部延遲腳本 我是body外部延遲腳本 我是底部外部延遲腳本 我是DOMContentLoaded裡面的腳本 我是window.onload裡面的腳本

 從輸出的結果中我們可以確定,只有IE9及以下瀏覽器支持內部延遲腳本,並且defer後的腳本都會在DOMContentLoaded事件之前觸發,因此也是會堵塞DOMContentLoaded事件的。

2.4 DEMO:IE<=9的defer特性bug

從2.3節中的demo可以看出,defer後的腳本還是能夠保持執行順序的,也就是按照添加的順序依次執行。而在IE<=9中,這個問題存在一個bug:假如我們向文檔中增加了多個defer的腳本,而且之前的腳本中有appendChild,innerHTML,insertBefore,replaceChild等修改了DOM的介面調用,那麼後面的腳本可能會先於該腳本執行。可以參考github的issue:https://github.com/h5bp/lazyweb-requests/issues/42

我們通過DEMO驗證一下,首先修改1.js的代碼為(這段代碼只為模擬,事實上這段代碼存在極大的性能問題):

document.body.innerHTML = "<div id='div'>我是後來加入的</div>";
document.body.innerHTML += "<div id='div'>我是後來加入的</div>";
document.body.innerHTML += "<div id='div'>我是後來加入的</div>";
document.body.innerHTML += "<div id='div'>我是後來加入的</div>";
document.body.innerHTML += "<div id='div'>我是後來加入的</div>";
document.body.innerHTML += "<div id='div'>我是後來加入的</div>";
document.body.innerHTML += "<div id='div'>我是後來加入的</div>";
alert("我是第1個腳本");

 2.js

alert("我是第2個腳本");

 修改HMTL中的代碼為:

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8"/>
    <title>defer bug in IE=9 test</title>
    <script src="1.js" type="text/javascript" defer="defer"></script>
    <script src="2.js" type="text/javascript" defer="defer"></script>
</head>
<body>
</body>
</html>

 正常情況下,瀏覽器中彈出框的順序肯定是:我是第1個腳本-》我是第2個腳本,然而在IE<=9中,執行結果卻為:我是第2個腳本-》我是第1個腳本,驗證了這個bug。

2.5 defer總結

在總結之前,首先要說一個註意點:正如標準中提到的,defer的腳本中不應該出現document.write的操作,瀏覽器會直接忽略這些操作。

總的來看,defer的作用一定程度上與將腳本放置在頁面底部有一定的相似,但由於IE<=9中的bug,如果頁面中出現多個defer時,腳本的執行順序可能會被打亂從而導致代碼依賴可能會出錯,因此實際項目中很少會使用defer特性,而將腳本代碼放置在頁面底部可以替代defer所提供的功能。

3 async特性

3.1 關於async腳本的執行時機

async特性是HTML5中引入的特性,使用方式為:async="async",我們首先看一下標準中對於async特性的相關描述:

async:If the async attribute is present, then the script will be executed asynchronously, as soon as it is available. 

需要指出,這裡的非同步,指的其實是非同步載入而不是非同步執行,也就是說,瀏覽器遇到一個async的script標簽時,會非同步的去載入(個人認為這個過程主要是下載的過程),一旦載入完畢就會執行代碼,而執行的過程肯定還是同步的,也就是阻塞的。我們可以通過下圖來綜合理解defer和async:

來源:https://html.spec.whatwg.org/multipage/scripting.html#attr-script-defer

這樣來看的話,async腳本的執行時機是無法確定的,因為腳本何時載入完畢也是不確定的。我們通過下麵的demo來感受一下:

async1.js

alert("我是非同步的腳本");

 HTML代碼:

<!DOCTYPE html>
<html>
<head lang="en">
    <meta charset="UTF-8">
    <title>async attribute test</title>
    <script src="/delayfile.php?url=http://localhost/js/load/async1.js&delay=2" async="async" type="text/javascript"></script>
    <script>
        alert("我是同步的腳本");
    </script>
</head>
<body>
</body>
</html>

 這裡我們借用了《瀏覽器環境下JavaScript腳本載入與執行探析之代碼執行順序》中的delayfile腳本來提供了一個延遲,這個腳本在支持async的瀏覽器中,彈框的順序一般是:我是同步的腳本-》我是非同步的腳本。

3.2 async的瀏覽器支持情況

下麵我們來看一下async特性的瀏覽器支持情況:

 

 可以看到,只有IE10+才支持async特性,opera mini不支持async特性,另外,async是不支持內部腳本的。

3.3 async總結

async指的非同步腳本,即腳本非同步載入,載入的過程不會造成阻塞,但是async的腳本的執行時機是不確定的,而且執行的順序也是不確定的,因此使用async的腳本應該是不依賴於任何代碼的腳本(比如第三方統計代碼或廣告代碼),否則就會導致執行出錯。

4 defer和async的優先順序問題

這一點比較好理解,標準中規定了:

[1]如果<script>元素同時定義了defer和async特性,則按async來處理(註意:對於不支持async的瀏覽器會直接忽略async特性)

[2]如果<script>元素只定義了defer,則按延遲腳本的方式處理

[3]如果<script>元素沒有定義defer也沒有定義async,則按正常情況處理,即:腳本立即載入和執行

 

由於經驗尚淺,文中難免會出現錯誤,歡迎指正錯誤和交流~~


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 強調標簽:斜體強調 粗體強調 直接用span控制也可以實現該效果 font-size font-weight分行標簽:水平橫線標簽: 效果: ...
  • 構造器模式簡單描述(看圖): 構造器用於創建特定類型對象——準備好對象以備使用,同時接收構造器可以使用的參數,以在第一次創建對象時,設置成員屬性和方法的值 1、創建對象 新對象創建的兩種方法 <code var newObject={}; var newObject=new object();...
  • 這裡要介紹的是一個jQuery插件:jquery.easysector.jsHtml5提供了強大的繪圖API,讓我們能夠使用javascript輕鬆繪製各種圖形。本文將主要講解使用HTML5繪製餅圖(統計圖)的方法。先看一下餅圖效果:http://hovertree.com/texiao/easys...
  • 1:接受文件http://stackoverflow.com/questions/24610996/how-to-get-uploaded-file-in-node-js-express-app-using-angular-file-upload可以用下列的第三方庫busboyandconnect-...
  • 時代的變遷,創業的大潮,越來越多的人關註了有點開發,越來越多的人瞭解了互聯網服務術語:PaaS、IaaS、SaaS、BaaS等。今天大家在開發App的時候這麼多複雜的雲服務如何來選擇呢?IaaS服務商大家提起馬上能想到的一定就是“阿裡雲”、“騰訊雲”、“微軟Azure”、“AWS”。這些都是IaaS...
  • 灌水評論示例:var http = require('http');var querystring = require('querystring');var postData = querystring.stringify({ content: '不錯不錯', cid: 348});va...
  • 前言:高程這本書真是神奇,每複習一遍,都會有新的收穫。話說我看書有個習慣,要是看得似懂非懂的地方就喜歡打個“?”。這次看到高程第七章“函數表達式”關於閉包與this對象的部分,發現已經積攢了2個問號了。之前過了兩遍都沒有完全弄明白! 好在如今對this的指向、函數的理解已經今非昔比,這一次終於讓我....
  • function iframeTimeOut(url, timeOut_callback, width, height) { /// /// iframe超時處理 /// /// iframe(src路徑) /// 超時後執形的操作 /// 寬 /// ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...