淺析script標(biāo)簽中的defer與async屬性
一、前言
看到的前輩寫(xiě)的代碼如下
<script src="#link("xxxx/xx/home/home.js")" type="text/javascript" async defer></script>
竟然同時(shí)有async
和defer
屬性,心想著肯定是前輩老司機(jī)的什么黑科技,兩個(gè)一塊兒肯定會(huì)發(fā)生什么神奇化學(xué)反應(yīng),于是趕緊懷著一顆崇敬的心去翻書(shū)翻文檔,先復(fù)習(xí)一下各自的定義。
二、調(diào)查一番
先看看async
和defer
各自的定義吧,翻開(kāi)紅寶書(shū)望遠(yuǎn)鏡,是這么介紹的
2.1 defer
這個(gè)屬性的用途是表明腳本在執(zhí)行時(shí)不會(huì)影響頁(yè)面的構(gòu)造。也就是說(shuō),腳本會(huì)被延遲到整個(gè)頁(yè)面都解析完畢后再運(yùn)行。因此,在<script>元素中設(shè)置defer
屬性,相當(dāng)于告訴瀏覽器立即下載,但延遲執(zhí)行。
HTML5規(guī)范要求腳本按照它們出現(xiàn)的先后順序執(zhí)行,因此第一個(gè)延遲腳本會(huì)先于第二個(gè)延遲腳本執(zhí)行,而這兩個(gè)腳本會(huì)先于DOMContentLoaded
事件執(zhí)行。在現(xiàn)實(shí)當(dāng)中,延遲腳本并不一定會(huì)按照順序執(zhí)行,也不一定會(huì)在DOMContentLoad
時(shí)間觸發(fā)前執(zhí)行,因此最好只包含一個(gè)延遲腳本。
2.2 async
這個(gè)屬性與defer
類(lèi)似,都用于改變處理腳本的行為。同樣與defer
類(lèi)似,async
只適用于外部腳本文件,并告訴瀏覽器立即下載文件。但與defer
不同的是,標(biāo)記為async
的腳本并不保證按照它們的先后順序執(zhí)行。
第二個(gè)腳本文件可能會(huì)在第一個(gè)腳本文件之前執(zhí)行。因此確保兩者之間互不依賴(lài)非常重要。指定async
屬性的目的是不讓頁(yè)面等待兩個(gè)腳本下載和執(zhí)行,從而異步加載頁(yè)面其他內(nèi)容。
概括來(lái)講,就是這兩個(gè)屬性都會(huì)使script標(biāo)簽異步加載,然而執(zhí)行的時(shí)機(jī)是不一樣的。引用segmentfault上的一個(gè)回答中的一張圖
藍(lán)色線(xiàn)代表網(wǎng)絡(luò)讀取,紅色線(xiàn)代表執(zhí)行時(shí)間,這倆都是針對(duì)腳本的;綠色線(xiàn)代表 HTML 解析。
也就是說(shuō)async
是亂序的,而defer
是順序執(zhí)行,這也就決定了async
比較適用于百度分析或者谷歌分析這類(lèi)不依賴(lài)其他腳本的庫(kù)。從圖中可以看到一個(gè)普通的<script>
標(biāo)簽的加載和解析都是同步的,會(huì)阻塞DOM的渲染,這也就是我們經(jīng)常會(huì)把<script>
寫(xiě)在<body>
底部的原因之一,為了防止加載資源而導(dǎo)致的長(zhǎng)時(shí)間的白屏,另一個(gè)原因是js可能會(huì)進(jìn)行DOM操作,所以要在DOM全部渲染完后再執(zhí)行。
2.3 really?
然而,這張圖(幾乎是百度搜到的唯一答案)是不嚴(yán)謹(jǐn)?shù)?,這只是規(guī)范的情況,大多數(shù)瀏覽器在實(shí)現(xiàn)的時(shí)候會(huì)作出優(yōu)化。
來(lái)看看chrome是怎么做的
《WebKit技術(shù)內(nèi)幕》:
1、當(dāng)用戶(hù)輸入網(wǎng)頁(yè)URL的時(shí)候,WebKit調(diào)用其資源加載器加載該URL對(duì)應(yīng)的網(wǎng)頁(yè)。
2、加載器依賴(lài)網(wǎng)絡(luò)模塊建立連接,發(fā)送請(qǐng)求并接受答復(fù)。
3、WebKit接收到各種網(wǎng)頁(yè)或者資源的數(shù)據(jù),其中某些資源可能是同步或異步獲取的。
4、網(wǎng)頁(yè)被交給HTML解釋器轉(zhuǎn)變成一系列的詞語(yǔ)(Token)。
5、解釋器根據(jù)詞語(yǔ)構(gòu)建節(jié)點(diǎn)(Node),形成DOM樹(shù)。
6、如果節(jié)點(diǎn)是JavaScript代碼的話(huà),調(diào)用JavaScript引擎解釋并執(zhí)行。
7、JavaScript代碼可能會(huì)修改DOM樹(shù)的結(jié)構(gòu)。
8、如果節(jié)點(diǎn)需要依賴(lài)其他資源,例如圖片、CSS、視頻等,調(diào)用資源加載器來(lái)加載他們,但是他們是異步的,不會(huì)阻礙當(dāng)前DOM樹(shù)的繼續(xù)創(chuàng)建;如果是JavaScript資源URL(沒(méi)有標(biāo)記異步方式),則需要停止當(dāng)前DOM樹(shù)的創(chuàng)建,直到JavaScript的資源加載并被JavaScript引擎執(zhí)行后才繼續(xù)DOM樹(shù)的創(chuàng)建。
所以,通俗來(lái)講,chrome瀏覽器首先會(huì)請(qǐng)求HTML文檔,然后對(duì)其中的各種資源調(diào)用相應(yīng)的資源加載器進(jìn)行異步網(wǎng)絡(luò)請(qǐng)求,同時(shí)進(jìn)行DOM渲染,直到遇到<script>
標(biāo)簽的時(shí)候,主進(jìn)程才會(huì)停止渲染等待此資源加載完畢然后調(diào)用V8引擎對(duì)js解析,繼而繼續(xù)進(jìn)行DOM解析。我的理解如果加了async
屬性就相當(dāng)于單獨(dú)開(kāi)了一個(gè)進(jìn)程去獨(dú)立加載和執(zhí)行,而defer
是和將<script>
放到<body>
底部一樣的效果。
三、實(shí)驗(yàn)一發(fā)
3.1 demo
為了驗(yàn)證上面的結(jié)論我們來(lái)測(cè)試一下
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Document</title> <link rel="stylesheet"> <link 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{這是第$個(gè)節(jié)點(diǎn)}*1000 </body> </html>
一個(gè)簡(jiǎn)單的demo,從各個(gè)CDN上引用了2個(gè)CSS3個(gè)JS,在body里面創(chuàng)建了1000個(gè)li。通過(guò)調(diào)整外部引用資源的位置和加入相關(guān)的屬性利用chrome的Timeline進(jìn)行驗(yàn)證。
3.2 放置在<head>內(nèi)
異步加載資源,但會(huì)阻塞<body>
的渲染會(huì)出現(xiàn)白屏,按照順序立即執(zhí)行腳本
3.3 放置在<body>底部
異步加載資源,等<body>
中的內(nèi)容渲染完畢后且加載完按順序執(zhí)行JS
3.3 放置在<head>頭部并使用async
異步加載資源,且加載完JS資源立即執(zhí)行,并不會(huì)按順序,誰(shuí)快誰(shuí)先上
3.4 放置在<head>頭部并使用defer
異步加載資源,在DOM渲染后之后再按順序執(zhí)行JS
3.5 放置在<head>頭部并同時(shí)使用async和defer
表現(xiàn)和async一致,開(kāi)了個(gè)腦洞,把這兩個(gè)屬性交換一下位置,看會(huì)不會(huì)有覆蓋效果,結(jié)果發(fā)現(xiàn)是一致的 = =、
綜上,在webkit引擎下,建議的方式仍然是把<script>
寫(xiě)在<body>
底部,如果需要使用百度谷歌分析或者不蒜子等獨(dú)立庫(kù)時(shí)可以使用async
屬性,若你的<script>
標(biāo)簽必須寫(xiě)在<head>頭部?jī)?nèi)可以使用defer
屬性
四、 兼容性
那么,揣摩一下前輩的心理,同時(shí)寫(xiě)上的原因是什么呢,兼容性?
上caniuse,async
在IE<=9時(shí)不支持,其他瀏覽器OK;defer
在IE<=9時(shí)支持但會(huì)有bug,其他瀏覽器OK;現(xiàn)象在這個(gè)issue
里有描述,這也就是“望遠(yuǎn)鏡”里建議只有一個(gè)defer
的原因。所以?xún)蓚€(gè)屬性都指定是為了在async
不支持的時(shí)候啟用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.
五、結(jié)論
其實(shí)這么講來(lái),最穩(wěn)妥的辦法還是把<script>
寫(xiě)在<body>
底部,沒(méi)有兼容性問(wèn)題,沒(méi)有白屏問(wèn)題,沒(méi)有執(zhí)行順序問(wèn)題,高枕無(wú)憂(yōu),不要搞什么defer
和async
的花啦~
目前只研究了chrome的webkit的渲染機(jī)制,F(xiàn)irefox和IE的有待繼續(xù)研究,圖片和CSS以及其他外部資源的渲染有待研究。
總結(jié)
以上就是這篇文章的全部?jī)?nèi)容了,希望本文的內(nèi)容對(duì)大家的學(xué)習(xí)或者工作能帶來(lái)一定的幫助,如果有疑問(wèn)大家可以留言交流。
- script標(biāo)簽中的async和defer詳細(xì)說(shuō)明與使用場(chǎng)景
- script標(biāo)簽中的defer和async使用技巧說(shuō)明
- JavaScript延遲加載之a(chǎn)sync與defer的應(yīng)用
- JS script腳本中async和defer區(qū)別詳解
- JavaScript無(wú)阻塞加載和defer、async詳解
- 關(guān)于Javascript中defer和async的區(qū)別總結(jié)
- JS中script標(biāo)簽defer和async屬性的區(qū)別詳解
- 淺談async、defer以普通script加載的區(qū)別
相關(guān)文章
原生js+canvas實(shí)現(xiàn)驗(yàn)證碼
這篇文章主要為大家詳細(xì)介紹了原生js+canvas實(shí)現(xiàn)驗(yàn)證碼,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2020-11-11JavaScript實(shí)現(xiàn)in-place思想的快速排序方法
這篇文章主要介紹了JavaScript實(shí)現(xiàn)in-place思想的快速排序方法的相關(guān)資料,非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友可以參考下2016-08-08js中substring和substr的詳細(xì)介紹與用法
這篇文章介紹了js中substring和substr的用法,有需要的朋友可以參考一下2013-08-08javascript實(shí)現(xiàn)動(dòng)態(tài)CSS換膚技術(shù)的腳本
javascript實(shí)現(xiàn)動(dòng)態(tài)CSS換膚技術(shù)的腳本...2007-06-06讓你的博文自動(dòng)帶上縮址的實(shí)現(xiàn)代碼,方便發(fā)到微博客上
添加以下代碼到你的博客中: (呵呵,抄襲至lulu Studio http://s8.hk/0itw)2010-12-12JS中g(shù)etElementsByClassName與classList兼容性問(wèn)題解決方案分析
這篇文章主要介紹了JS中g(shù)etElementsByClassName與classList兼容性問(wèn)題解決方案,結(jié)合實(shí)例形式分析了getElementsByClassName與classList的使用方法、原理及兼容性問(wèn)題的處理技巧,需要的朋友可以參考下2019-08-08