首頁  >  文章  >  web前端  >  淺析script標籤中的defer與async屬性

淺析script標籤中的defer與async屬性

高洛峰
高洛峰原創
2016-12-03 13:51:061041瀏覽

一、前言

看到的前輩寫的代碼如下

<script src="#link("xxxx/xx/home/home.js")" type="text/javascript" async defer></script>

   

竟然同時有async和defer屬性,心想著肯定是前輩老司機的什麼黑科技,兩個一塊一塊兒肯定會發生什麼神奇化學反應,於是趕緊懷著一顆崇敬的心去翻書翻文檔,先複習一下各自的定義。

二、調查一番

先看看async和defer各自的定義吧,翻開紅寶書望遠鏡,是這麼介紹的

2.1 defer

這個屬性的用途是表明腳本在執行時不會影響頁面的構造。也就是說,腳本會被延遲到整個頁面都解析完畢後再執行。因此,在<script>元素中設定defer屬性,相當於告訴瀏覽器立即下載,但延遲執行。 </script>

HTML5規範要求腳本按照它們出現的先後順序執行,因此第一個延遲腳本會先於第二個延遲腳本執行,而這兩個腳本會先於DOMContentLoaded事件執行。在現實當中,延遲腳本並不一定會依照順序執行,也不一定會在DOMContentLoad時間觸發前執行,因此最好只包含一個延遲腳本。

2.2 async

這個屬性與defer類似,都用來改變處理腳本的行為。同樣與defer類似,async只適用於外部腳本文件,並告訴瀏覽器立即下載文件。但與defer不同的是,標記為async的腳本並不保證按照它們的先後順序執行。

第二個腳本檔案可能會在第一個腳本檔案之前執行。因此確保兩者之間互不依賴非常重要。指定async屬性的目的是不讓頁面等待兩個腳本下載和執行,從而非同步載入頁面其他內容。

概括來講,就是這兩個屬性都會使script標籤異步加載,然而執行的時機是不一樣的。引用segmentfault上的一個答案中的一張圖

淺析script標籤中的defer與async屬性

藍色線代表網路讀取,紅色線代表執行時間,這兩個都是針對腳本的;綠色線代表 HTML 解析。

也就是說async是亂序的,而defer是順序執行,這也就決定了async比較適用於百度分析或谷歌分析這類不依賴其他腳本的函式庫。從圖中可以看到一個普通的<script>標籤的載入和解析都是同步的,會阻塞DOM的渲染,這就是我們常常會把<script>寫在<body>底部的原因之一,為了防止載入資源而導致的長時間的白屏,另一個原因是js可能會進行DOM操作,所以要在DOM全部渲染完後再執行。 </script>

2.3 really?

然而,這張圖(幾乎是百度搜到的唯一答案)是不嚴謹的,這只是規範的情況,大多數瀏覽器在實現的時候會作出優化。

來看看chrome是怎麼做的

《WebKit技術內幕》:

      1、當使用者輸入網頁URL的時候,WebKit調用其資源載入器載入該URL對應的網頁。

      2、載入器依賴網路模組建立連接,發送請求並接受答案。

      3、WebKit接收到各種網頁或資源的數據,其中某些資源可能是同步或非同步取得的。

      4、網頁交給HTML解釋者轉換成一系列的字詞(Token)。

      5、解譯器依照字詞建構節點(Node),形成DOM樹。

      6.如果節點是JavaScript程式碼的話,呼叫JavaScript引擎解釋並執行。

      7.JavaScript程式碼可能會修改DOM樹的結構。

      8、如果節點需要依賴其他資源,例如圖片、CSS、視訊等,請呼叫資源載入器來載入他們,但是他們是異步的,不會阻礙當前DOM樹的繼續創建;如果是JavaScript資源URL(沒有標記非同步方式),則需要停止目前DOM樹的創建,直到JavaScript的資源載入並被JavaScript引擎執行後才繼續DOM樹的創建。

所以,通俗來講,chrome瀏覽器首先會請求HTML文檔,然後對其中的各種資源調用相應的資源加載器進行異步網絡請求,同時進行DOM渲染,直到遇到<script>標籤的時候,主程序才會停止渲染等待此資源載入完畢然後呼叫V8引擎對js解析,然後繼續進行DOM解析。我的理解如果加了async屬性就相當於單獨開了一個進程去獨立加載和執行,而defer是和將<script>放到<body>底部一樣的效果。 </script>

三、實驗一發

3.1 demo

為了驗證上面的結論我們來測試一下

<!DOCTYPE html>
<html lang="en">
<head>
 <meta charset="UTF-8">
 <title>Document</title>
 <link href="http://libs.baidu.com/bootstrap/3.0.3/css/bootstrap.css" rel="stylesheet">
 <link href="http://cdn.staticfile.org/foundation/6.0.1/css/foundation.css" rel="stylesheet">
 <script src="http://lib.sinaapp.com/js/angular.js/angular-1.2.19/angular.js"></script>
 <script src="http://libs.baidu.com/backbone/0.9.2/backbone.js"></script>
 <script src="http://libs.baidu.com/jquery/2.0.0/jquery.js"></script>
</head>
<body>
 ul>li{这是第$个节点}*1000
</body>
</html>

   

一個簡單的demo,從各個CDN上引用了2個CSS3個JS,在body裡面創建了1000個li。透過調整外部引用資源的位置和加入相關的屬性利用chrome的Timeline進行驗證。

3.2 放置在

淺析script標籤中的defer與async屬性

異步加載資源,但會阻塞

的渲染會出現白屏,按照順序立即執行腳本

3.3 放置在

底部

淺析script標籤中的defer與async屬性

淺析script標籤中的defer與async屬性

淺析script標籤中的defer與async屬性

非同步載入資源,等中的內容渲染完畢後且載入完依序執行JS

3.3 放置在頭部並使用async

淺析script標籤中的defer與async屬性

異步載入資源,且載入完JS資源立即執行,並不會按順序,誰快誰先上

3.4 放置在頭部並使用defer


異步加載資源,在DOM渲染後之後再按順序執行JS

3.5 放置在頭部並同時使用async和defer

表現和async一致,開了個腦洞,把這兩個屬性交換一下位置,看會不會有覆蓋效果,結果發現是一致的= = 、

綜上,在webkit引擎下,建議的方式仍然是把<script>寫在<body>底部,如果需要使用百度谷歌分析或者不蒜子等獨立庫時可以使用async屬性,若你的< script>標籤必須寫在<head>頭部內可以使用defer屬性<p>四、 相容性<p><p>那麼,揣摩一下前輩的心理,同時寫上的原因是什麼呢,相容性?<br/>上caniuse, async在IE<=9時不支持,其他瀏覽器OK;defer在IE<=9時支持但會有bug,其他瀏覽器OK;現像在這個issue裡有描述,這也就是“望遠鏡”裡建議只有一個defer的原因。所以兩個屬性都指定是為了在async不支援的時候啟用defer,但defer在某些情況下還是有bug。 🎜🎜The defer attribute may be specified even if the async attribute is specified, to cause legacy Web browsers that only support defer (and not async) to fall back to the de behavior in lfering soommwion sdefr.五、結論🎜🎜🎜其實這麼講來,最穩健的辦法還是把<script>寫在<body>底部,沒有相容性問題,沒有白屏問題,沒有執行順序問題,高枕無憂,不要搞什麼defer和async的花啦~🎜🎜目前只研究了chrome的webkit的渲染機制,Firefox和IE的有待繼續研究,圖片和CSS以及其他外部資源的渲染有待研究。 🎜🎜🎜🎜</script>
陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn