今天在作一個小需的時候,突然看到前輩一句吊炸天的代碼javascript
<script src="#link("xxxx/xx/home/home.js")" type="text/javascript" async defer></script>
臥槽,居然同時有async
和defer
屬性,心想着確定是前輩老司機的什麼黑科技,兩個一起確定會發生什麼神奇化學反應,因而趕忙懷着一顆崇敬的心去翻書翻文檔,先複習一下各自的定義。css
先看看async
和defer
各自的定義吧,翻開紅寶書望遠鏡,是這麼介紹的html
這個屬性的用途是代表腳本在執行時不會影響頁面的構造。也就是說,腳本會被延遲到整個頁面都解析完畢後再運行。所以,在
<script>
元素中設置defer
屬性,至關於告訴瀏覽器當即下載,但延遲執行。html5HTML5規範要求腳本按照它們出現的前後順序執行,所以第一個延遲腳本會先於第二個延遲腳本執行,而這兩個腳本會先於
DOMContentLoaded
事件執行。在現實當中,延遲腳本並不必定會按照順序執行,也不必定會在DOMContentLoad
時間觸發前執行,所以最好只包含一個延遲腳本。java
這個屬性與
defer
相似,都用於改變處理腳本的行爲。一樣與defer
相似,async
只適用於外部腳本文件,並告訴瀏覽器當即下載文件。但與defer
不一樣的是,標記爲async
的腳本並不保證按照它們的前後順序執行。jquery第二個腳本文件可能會在第一個腳本文件以前執行。所以確保二者之間互不依賴很是重要。指定
async
屬性的目的是不讓頁面等待兩個腳本下載和執行,從而異步加載頁面其餘內容。git
歸納來說,就是這兩個屬性都會使script標籤異步加載,然而執行的時機是不同的。引用segmentfault上的一個回答中的一張圖藍色線表明網絡讀取,紅色線表明執行時間,這倆都是針對腳本的;綠色線表明 HTML 解析。github
也就是說async
是亂序的,而defer
是順序執行,這也就決定了async
比較適用於百度分析或者谷歌分析這類不依賴其餘腳本的庫。從圖中能夠看到一個普通的<script>
標籤的加載和解析都是同步的,會阻塞DOM的渲染,這也就是咱們常常會把<script>
寫在<body>
底部的緣由之一,爲了防止加載資源而致使的長時間的白屏,另外一個緣由是js可能會進行DOM操做,因此要在DOM所有渲染完後再執行。web
然而,這張圖(幾乎是百度搜到的惟一答案)是不嚴謹的,這只是規範的狀況,大多數瀏覽器在實現的時候會做出優化。chrome
來看看chrome是怎麼作的
《WebKit技術內幕》:
當用戶輸入網頁URL的時候,WebKit調用其資源加載器加載該URL對應的網頁。
加載器依賴網絡模塊創建鏈接,發送請求並接受答覆。
WebKit接收到各類網頁或者資源的數據,其中某些資源多是同步或異步獲取的。
網頁被交給HTML解釋器轉變成一系列的詞語(Token)。
解釋器根據詞語構建節點(Node),造成DOM樹。
若是節點是JavaScript代碼的話,調用JavaScript引擎解釋並執行。
JavaScript代碼可能會修改DOM樹的結構。
若是節點須要依賴其餘資源,例如圖片、CSS、視頻等,調用資源加載器來加載他們,可是他們是異步的,不會阻礙當前DOM樹的繼續建立;若是是JavaScript資源URL(沒有標記異步方式),則須要中止當前DOM樹的建立,直到JavaScript的資源加載並被JavaScript引擎執行後才繼續DOM樹的建立。
因此,通俗來說,chrome瀏覽器首先會請求HTML文檔,而後對其中的各類資源調用相應的資源加載器進行異步網絡請求,同時進行DOM渲染,直到遇到<script>
標籤的時候,主進程纔會中止渲染等待此資源加載完畢而後調用V8引擎對js解析,繼而繼續進行DOM解析。個人理解若是加了async
屬性就至關於單獨開了一個進程去獨立加載和執行,而defer
是和將<script>
放到<body>
底部同樣的效果。
爲了驗證上面的結論咱們來測試一下
<!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進行驗證。
<head>
內
異步加載資源,但會阻塞<body>
的渲染會出現白屏,按照順序當即執行腳本
<body>
底部
異步加載資源,等<body>
中的內容渲染完畢後且加載完按順序執行JS
<head>
頭部並使用async
異步加載資源,且加載完JS資源當即執行,並不會按順序,誰快誰先上
<head>
頭部並使用defer
異步加載資源,在DOM渲染後以後再按順序執行JS
<head>
頭部並同時使用async
和defer
表現和async
一致,開了個腦洞,把這兩個屬性交換一下位置,看會不會有覆蓋效果,結果發現是一致的 = =、
綜上,在webkit引擎下,建議的方式仍然是把<script>
寫在<body>
底部,若是須要使用百度谷歌分析或者不蒜子等獨立庫時可使用async
屬性,若你的<script>
標籤必須寫在<head>
頭部內可使用defer
屬性
那麼,揣摩一下前輩的心理,同時寫上的緣由是什麼呢,兼容性?
上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 defer behavior instead of the synchronous blocking behavior that is the default.
其實這麼講來,最穩妥的辦法仍是把<script>
寫在<body>
底部,沒有兼容性問題,沒有白屏問題,沒有執行順序問題,高枕無憂,不要搞什麼defer
和async
的花啦~
目前只研究了chrome的webkit的渲染機制,Firefox和IE的有待繼續研究,圖片和CSS以及其餘外部資源的渲染有待研究。
更多信息在 這裏