瀏覽器中script標簽加載順序是如何的呢?這個問題折騰了好幾次了,之前弄清楚了以后,覺得做不做筆記的無關(guān)緊要,可是后來發(fā)現(xiàn),隨著的時間的推移,知識逐漸被遺忘。然而更悲催的是,下次再遇到同樣的問題的時候,腦中僅僅有一個模糊的印象,一切又得重新開始,關(guān)鍵是每一次又要花掉不少時間。廢話不多,開始正題。
從瀏覽器加載HTML文件開始說起,如果HTML文件中引用了外部腳步文件,比如script標簽,那么它是什么時候加載這個script標簽的呢?如果有多個script標簽,是不是它們是按順序加載的呢?后面script標簽的加載會不會等待前面script標簽加載完畢后才開始呢?。。。。那defer和async又如何呢?下面一 一來探討。首先,瀏覽器在加載HTML文件的時候,并不是邊加載邊解析,它是得到一份完整的HTML文件后才開始解析,而script標簽是在HTML文件解析的時候才加載外部文件,理由很簡單,因為只有在解析時瀏覽器才知道某個標簽是做什么用的。有圖有真相,看下圖。
這是主文件的截圖(index.jsp):
腳本文件的截圖(one.jsp):
另一個腳本文件的截圖(two.jsp):
最后一個腳本文件(websocket.jsp):
現(xiàn)在,向index.jsp文件發(fā)送一個請求,控制臺輸出是(chrome):
說明了什么?說明了script標簽加載文件的時候是在HTML文件解析開始后。不知道有沒有人發(fā)現(xiàn),one.jsp文件并沒有阻塞two.jsp和websocket.jsp文件的加載。這是因為,新版本的瀏覽器可以同時并發(fā)多個http請求,而我的瀏覽器時最新版本的。看到這里,可能有人會認為script標簽引用外部文件的加載是按照它們在文檔中出現(xiàn)的順序加載的,起初我也是這么認為的,但事實似乎不是這樣。再次向index.jsp文件發(fā)送一個請求,結(jié)果如下圖:
上面紅色的1和2分別表示第一次和第二次請求,結(jié)果說明,script標簽并不是嚴格按照它們在文檔中出現(xiàn)的順序來加載外部文件的。但是,它們確實是按照在文檔中出現(xiàn)的順序來執(zhí)行的:
上述情況在其它瀏覽器中的情況如何呢?先來看看IE(IE11):
我試了多次,每次結(jié)果都一樣,上面是其中兩次的截圖,結(jié)果似乎表明,IE中是嚴格按照順序加載的,并且前面的script標簽加載過程中會阻塞后面script標簽的加載。
再來看看在火狐中的結(jié)果:
我試了幾次,這是前兩次的結(jié)果,似乎也表明并不是按照順序加載的,而且one.jsp文件也并沒有阻塞后面websocket.jsp的加載。可是接下來幾次的結(jié)果都是這樣:
它們又按照順序加載了,雖然前面的文件依然沒有阻塞后面文件的加載。IE會一直阻塞后面文件的加載。前面提到,新版本的瀏覽器可以同時并發(fā)多個http請求(據(jù)前面的結(jié)果,這一點在IE中似乎不是),那么,瀏覽器可以同時并發(fā)多少個請求呢?我們來試試。修改一下index.jsp文件,其中script標簽如下:
其中websocket標簽的內(nèi)容為:
其他標簽的內(nèi)容都類似這樣:
下面是在chrome中截圖:
我試了幾次,每次都是誰一樣,從中可以看出,chrome中最大并發(fā)數(shù)為6。
下面再來看看IE中的結(jié)果:
與預期一樣,IE一如既往的阻塞了后面腳本的加載。再來看看火狐:
還是與預期一樣,火狐與chrome的表現(xiàn)類似,一樣是六個并發(fā)。
下面來看看defer如何影響腳本的加載。javascript高級程序設(shè)計(第三版)中指出,defer會指示腳本立即加載,但會延遲到頁面解析完畢后在執(zhí)行,并且defer指定的script標簽不會再影響頁面的解析,言外之意是defer指定的標簽即使加載過程中發(fā)生阻塞也不會阻塞后面標簽的解析。我們來看看效果。先修改index.jsp中的文件,如下圖:
另外監(jiān)聽在index.jsp中DOMContentLoaded事件:
其他文件的內(nèi)容不變,現(xiàn)在來發(fā)生一個請求試試:
結(jié)果真是出乎意料,再一次請求試試:
變化不大,從上面的結(jié)果分析,defer指示的 script標簽似乎是等到其他標簽加載完成后再加載,即使同時的http請求還沒達到最大并發(fā)數(shù),同時,前后兩次請求中,defer指示的標簽一直是按出現(xiàn)的先后順序加載的。還有就是,defer指示的標簽在DOMContentLoaded事件前執(zhí)行。
先來驗證一下defer指示的標簽是不是一直按照先后順序加載的,我們來修改下index.jsp文件:
其他地方不變,發(fā)送一個請求看看:
看來并不是這樣,加載還是不嚴格按照script標簽在文檔中的順序。
把index.jsp文件修改為原樣:
發(fā)送一個請求,看看在火狐中的結(jié)果:
結(jié)果當真是出乎意料,第一與chrome‘分道揚鑣’,再來試一次:
還是一樣。再來看看在IE中的結(jié)果:
見鬼,IE難得的出現(xiàn)了并發(fā)并且出現(xiàn)了6個并發(fā)(5個defer一個非defer),并且defer推遲到正常標簽執(zhí)行完畢后執(zhí)行。我們來看看IE最大并發(fā)數(shù)是多少,修改index.jsp文件如下:
發(fā)送一個請求看看:
同樣是6,并且也不再按照script標簽出現(xiàn)的順序加載了。前面提到,IE中正常script標簽會阻塞后面正常script標簽的加載,現(xiàn)在來看看它是否也阻塞defer指示的script標簽的加載。修改index.jsp文件:
發(fā)送一個請求看看:
還是會阻塞defer指示的script標簽。再來修改index.jsp文件看看:
發(fā)送一個請求:
并發(fā)數(shù)是5,還是一樣,即使沒達到最大并發(fā)數(shù),正常script標簽一樣阻塞后面標簽的加載。
現(xiàn)在來看看async是如何影響script標簽的加載與執(zhí)行的,修改index.jsp文件:
在 chrome中發(fā)生 一個請求:
在chrome中倒是與defer一樣,同時,有兩點不一樣,一點是,async指示的script標簽的執(zhí)行在DOMContentLoaded事件之后,另一點是,即使四個async標簽加載完成的先后順序是:four.jsp、three.jsp、one.jsp、two.jsp,但它們的執(zhí)行順序卻是:one.jsp、two.jsp、four.jsp、three.jsp,幾乎無序可尋。
再來看看在火狐中的結(jié)果:
和chrome的差別很大,async指示的四個文件加載完成的先后順序是:four.jsp、two.jsp、one.jsp、three.jsp,而他們執(zhí)行的先后順序是:four.jsp、two.jsp、one.jsp、three.jsp。加載完成順序與執(zhí)行順序一致。但是,他們完全沒有等到正常script標簽執(zhí)行就開始執(zhí)行了。為了結(jié)果更可靠一些,再來次請求:
async指示的四個文件加載完成的先后順序是:four.jsp、two.jsp、one.jsp、three.jsp,而他們執(zhí)行的先后順序是:four.jsp、two.jsp、one.jsp、three.jsp,加載完成順序與執(zhí)行順序一致,并且加載完成就執(zhí)行。
最后來看看IE,發(fā)送一個請求:
從上述結(jié)果可以看出,IE和火狐對待async標簽的方式差不多,不同的一點是,IE中async標簽與chrome中一樣,并不是按照下載完成順序執(zhí)行。再來看看IE會不會阻塞async標簽的加載,修改index.jsp文件:
發(fā)送一個請求:
從結(jié)果可以看出,IE中正常標簽同樣阻塞了async標簽的加載。async標簽同樣不按加載完成順序執(zhí)行。
總結(jié)一下,三個瀏覽器最大并發(fā)數(shù)為6。在chrome中先全部加載正常標簽再加載defer和async標簽(但正常標簽加載的阻塞可能會阻塞其他標簽的加載),同時,defer標簽在DOMContentLoaded事件前執(zhí)行,且按照script標簽在文檔中出現(xiàn)的順序執(zhí)行,async標簽加載完畢就可能執(zhí)行,不會等待正常標簽的執(zhí)行,無論是在DOMContentLoaded事件之前還是之后,且執(zhí)行順序與async標簽加載完成先后順序無關(guān)。除此之外,在chrome中在請求并發(fā)數(shù)沒達到最大并發(fā)數(shù)時,所有標簽都不會阻塞其他標簽的加載。在火狐中,所有標簽都是并發(fā)加載,正常標簽不會阻塞defer標簽和async標簽的加載,但是defer標簽會在正常標簽執(zhí)行完畢后再按在文檔中出現(xiàn)的順序執(zhí)行。async標簽則是加載完畢后就執(zhí)行,而不會等待正常 script標簽的執(zhí)行,同時他們的加載順序與他們在文檔中出現(xiàn)的順序也沒關(guān)系。在IE中,正常標簽加載的阻塞會阻塞其他在文檔中出現(xiàn)在它后面的script標簽的加載,無論是正常標簽還是defer和async標簽。defer和async標簽加載的阻塞不會阻塞其他在文檔中出現(xiàn)在它后面的script標簽的加載,無論是正常標簽還是defer和async標簽。defer標簽會推遲到正常標簽執(zhí)行完畢后再按在文檔中出現(xiàn)的順序執(zhí)行,并且async標簽的執(zhí)行并不是按照其加載完成先后順序,async標簽的執(zhí)行與chrome一樣,無序可尋。無論是在火狐還是IE或者chrome中,defer和async標簽都可以在DOMContentLoaded事件前執(zhí)行,但只有async標簽可以在DOMContentLoaded事件后執(zhí)行。簡單來講,async標簽加載完成后就可能執(zhí)行且執(zhí)行可能無序,而不會等待其他標簽的執(zhí)行,而defer則是加載完成后需要等待正常標簽執(zhí)行完畢后才會執(zhí)行。defer標簽和正常標簽一定會在DOMContentLoaded事件之前執(zhí)行完畢,而async標簽可能會在DOMContentLoaded事件之前或之后執(zhí)行。關(guān)于defer和async與DOMContentLoaded事件的關(guān)系,留待下一篇文章詳細探討。
下面的一段話摘取某篇博文:
Both async and defer scripts begin to download immediately without pausing the parser and both support an optional onload handler to address the common need to perform initialization which depends on the script. The difference between async and defer centers around when the script is executed. Each async script executes at the first opportunity after it is finished downloading and before the window’s load event. This means it’s possible (and likely) that async scripts are not executed in the order in which they occur in the page. The defer scripts, on the other hand, are guaranteed to be executed in the order they occur in the page. That execution starts after parsing is completely finished, but before the document’s DOMContentLoaded event.
摘自http://w3c.github.io/html/semantics-scripting.html#data-block:
The async and defer attributes are?boolean attributes?that indicate how the script should be executed.Classic scripts?may specify?defer?or?async;?module scripts?may specify?async.
There are several possible modes that can be selected using these attributes, and depending on the script’s?type.
For?classic scripts, if the?async?attribute is present, then the classic script will be fetched?in parallel?to parsing and evaluated as soon as it is available (potentially before parsing completes). If the?async?attribute is not present but the?defer?attribute is present, then the?classic script?will be fetched?in parallel?and evaluated when the page has finished parsing. If neither attribute is present, then the script is fetched and evaluated immediately, blocking parsing until these are both complete.
For?module scripts, if the?async?attribute is present, then the module script and all its dependencies will be fetched?in parallel?to parsing, and the module script will be evaluated as soon as it is available (potentially before parsing completes). Otherwise, the module script and its dependencies will be fetched?in parallel?to parsing and evaluated when the page has finished parsing. (The?defer?attribute has no effect on module scripts.)
classic script and module script:
The?script?element allows authors to include dynamic script and data blocks in their documents. The element does not?represent?content for the user.
The type attribute allows customization of the type of script represented :
Omitting the attribute, or setting it to a?JavaScript MIME type, means that the script is a?classic script, to be interpreted according to the JavaScript?Script?top-level production. Classic scripts are affected by the?charset,?async, and?defer?attributes. Authors should omit the attribute, instead of redundantly giving a?JavaScript MIME type.
Setting the attribute to an?ASCII case-insensitive?match for the string "module" means that the script is a?module script, to be interpreted according to the JavaScript?Module?top-level production. Module scripts are not affected by the?charset?and?defer?attributes.
Setting the attribute to any other value means that the script is a data block, which is not processed. None of the?script?attributes (except?type?itself) have any effect on?data blocks. Authors must use a?valid MIME type?that is not a?JavaScript MIME type?to denote?data blocks.