一、前言
看到的前輩寫的代碼如下
<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上的一個答案中的一張圖
藍色線代表網路讀取,紅色線代表執行時間,這兩個都是針對腳本的;綠色線代表 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 放置在
內異步加載資源,但會阻塞
的渲染會出現白屏,按照順序立即執行腳本3.3 放置在
底部 非同步載入資源,等中的內容渲染完畢後且載入完依序執行JS3.3 放置在頭部並使用async 異步載入資源,且載入完JS資源立即執行,並不會按順序,誰快誰先上3.4 放置在頭部並使用defer表現和async一致,開了個腦洞,把這兩個屬性交換一下位置,看會不會有覆蓋效果,結果發現是一致的= = 、