做了好幾年前端了,最近想進個大廠,參加了阿里、百度、今日頭條的面試,被徹底的打擊了,感覺自己的基礎不是很牢,所以萌發了重學前端的想法,向習以為常的知識/技能,多問一個為什麼,多瞭解一下原理,或者原始碼
今天我們來聊聊html中資源載入和執行的順序問題。
做了幾年前端了,可是當瀏覽器獲取到html檔案後怎麼載入執行檔案,這些問題你能回答嗎
- 按照什麼順序載入檔案?
- 每次能載入多少個檔案?
- css沒載入完會執行js嗎?
- async和defer的區別是什麼?
- DOMContentLoaded事件和onload事件,什麼時間觸發?
為了搞清楚這些問題,我們來做下試驗,我用的是chrome瀏覽器。
html中資源載入順序及數量
我們先構造一個html檔案,在head裡面交替載入10個css檔案和10個js檔案,這幾個檔案地址是我用本地php服務建立的,都延遲3秒返回資料,以便我們清楚觀察效果
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<link rel="stylesheet" href="http://test.com/test/css1">
<script src="http://test.com/test/js1"></script>
<link rel="stylesheet" href="http://test.com/test/css2">
<script src="http://test.com/test/js2"></script>
<link rel="stylesheet" href="http://test.com/test/css3">
<script src="http://test.com/test/js3"></script>
<link rel="stylesheet" href="http://test.com/test/css4">
<script src="http://test.com/test/js4"></script>
<link rel="stylesheet" href="http://test.com/test/css5">
<script src="http://test.com/test/js5"></script>
<link rel="stylesheet" href="http://test.com/test/css6">
<script src="http://test.com/test/js6"></script>
<link rel="stylesheet" href="http://test.com/test/css7">
<script src="http://test.com/test/js7"></script>
<link rel="stylesheet" href="http://test.com/test/css8">
<script src="http://test.com/test/js8"></script>
<link rel="stylesheet" href="http://test.com/test/css9">
<script src="http://test.com/test/js9"></script>
<link rel="stylesheet" href="http://test.com/test/css10">
<script src="http://test.com/test/js10"></script>
</head>
<body>
</body>
</html>
複製程式碼
css和js的地址我是使用php框架構造的,目的是為了使用php的sleep延遲返回資源,好讓我們更清楚的看到時序,你可以通過node或其他後臺自行構造
//js檔案,等待3s後返回
public function actionJs1(){
sleep(3);
return "console.log('js1111111')";
}
//css檔案,等待3s返回
public function actionCss1(){
sleep(3);
}
複製程式碼
我們通過chrome來看下載入時序,可以看到,瀏覽器首先下載html檔案,下載之後緊接著按照文件順序請求css1、js1、css2、js2、css3、js3,可以看到瀏覽器一次可以載入6個檔案,載入完成之後再載入6個,直到全部載入完成。
而載入順序實際反覆測試幾次,均為【css1、js1、css2、js2、css3、js3】、【css4、css5、css6、css7、css8、css9】、【css10、js4、js5、js6、js7、js8】、【js9、js10】,可以看出,有兩個特點:整體來說是按照文件在html中出現順序進行載入的,但是會部分優先載入css檔案
為了證實上面的猜想,有對html程式碼進行了改造,10個css全放前面,10個js全放後面
<link rel="stylesheet" href="http://test.com/test/css1">
......
<link rel="stylesheet" href="http://test.com/test/css10">
<script src="http://test.com/test/js1"></script>
......
<script src="http://test.com/test/js10"></script>
複製程式碼
果然,瀏覽器優先把前面10個css先載入完畢,然後才載入js
而如果我們把10個js放到10個css檔案前面呢
<script src="http://test.com/test/js1"></script>
......
<script src="http://test.com/test/js10"></script>
<link rel="stylesheet" href="http://test.com/test/css1">
......
<link rel="stylesheet" href="http://test.com/test/css10">
複製程式碼
可以看到,瀏覽器先載入了6個js,然後又優先把10個css載入完,之後才載入js
接下來在body中再新增10個圖片,看看css、js、圖片,這三種資源的載入順序,不用測試我們應該也能想到,圖片應該在css和js之後,實驗驗證一下。
構造測試html,每個資源設定6個,順序如下
<link rel="stylesheet" href="http://test.com/test/css1">
......
<link rel="stylesheet" href="http://test.com/test/css6">
<img src="http://test.com/test/img1"/>
......
<img src="http://test.com/test/img6"/>
<script src="http://test.com/test/js1"></script>
......
<script src="http://test.com/test/js6"></script>
複製程式碼
chrome檔案載入時序為,先載入6個css、再載入6個js,最後載入6個圖片
瀏覽器每次只能載入6個檔案,那還能不能再多載入一些呢,假如載入的資源域名不同,會發生什麼?
//前六個css域名為test.com
<link rel="stylesheet" href="http://test.com/test/css1">
......
<link rel="stylesheet" href="http://test.com/test/css6">
//後六個css域名為m.test.com
<link rel="stylesheet" href="http://m.test.com/test/css1">
......
<link rel="stylesheet" href="http://m.test.com/test/css6">
複製程式碼
可以看到,瀏覽器一次性載入了12個檔案
結論
1.瀏覽器載入檔案,整體順序是按照檔案在html中出現的順序進行載入
2.但是會優先載入css、然後載入js、最後載入圖片
3.同一個域名的資源,谷歌瀏覽器每次載入6個,不同域名的資源可以並行載入
看到這裡,我們應該會效能優化有了一些想法了吧,比如使用不同域名可以提高整體載入速度
html中js執行時機
js會在載入成功立即執行嗎,還是會受到別的因素影響呢?我們先來測試一下吧,假如css延遲3s返回,js取消延遲立即返回,看看會發生什麼
//css延遲3s返回資料
<link rel="stylesheet" href="http://test.com/test/css1">
//js立即返回,console.log('js1')
<script src="http://test.com/test/js1"></script>
複製程式碼
重新整理頁面可以看到儘管js很快就載入完成了,但是並沒有立即執行,等了3s左右才執行,也就是在css載入完成後才執行,即css的載入會阻塞js的執行,那是不是因為css在js前面的原因呢?
我們調換一下css和js的順序,則可以看到js立即列印,並不會等待css的載入
//js立即返回,console.log('js1')
<script src="http://test.com/test/js1"></script>
//css延遲3s返回資料
<link rel="stylesheet" href="http://test.com/test/css1">
複製程式碼
結論
- 如果css在js前面,則css的載入會阻塞js的執行,即css載入完畢後,執行js
- 如果css在js後面,則不會阻塞js的執行
接下來我們看看兩個js的載入執行順序,先從最普通的開始
//js1延遲5s返回資料,console.log('js1')
<script src="http://test.com/test/js1"></script>
//js2延遲3s返回資料,console.log('js2')
<script src="http://test.com/test/js2"></script>
<script>
console.log('js3')
</script>
複製程式碼
等待5s後,控制檯先後列印 js1 js2 js3,雖然js2提前載入完成,但是仍然要等待前面的js1載入執行完畢才能執行
js非同步載入執行有兩種方式async和defer,接下來我們給js新增async屬性看看
//js1延遲5s返回資料,console.log('js1')
<script src="http://test.com/test/js1" async></script>
//js2延遲3s返回資料,console.log('js2')
<script src="http://test.com/test/js2" async></script>
<script>
console.log('js3')
</script>
複製程式碼
控制檯立即列印順序為
js3
js2
js1
複製程式碼
可見新增async屬性js不會阻塞其後面js的執行,誰先載入完成誰先執行,接下來新增defer屬性。
//js1延遲5s返回資料,console.log('js1')
<script src="http://test.com/test/js1" defer></script>
//js2延遲3s返回資料,console.log('js2')
<script src="http://test.com/test/js2" defer></script>
<script>
console.log('js3')
</script>
複製程式碼
控制檯列印順序如下,可以看出,新增defer屬性的js,不會阻塞後面的js執行,但是多個新增defer的js,仍然按照既有順序執行。
js3
js1
js2
複製程式碼
下面是一張經典js載入執行時機對比圖,
-
瀏覽器遇到沒有新增非同步屬性的js,會立即載入並執行,也就是說會阻塞html的解析
-
瀏覽器遇到新增async屬性的js會立即載入(當然了,如谷歌瀏覽器,就算沒有新增async,它也會提前識別html中的js檔案進行載入,載入時機如第一部分討論),並在js載入完畢之後立即執行;多個async屬性的js誰先載入完成誰先執行
-
瀏覽器遇到新增defer屬性的js會立即載入,但是不會立即執行,而是會在html解析完成之後,DOMContentLoaded觸發之前執行;多個defer屬性的js會按照文件順序執行
上面提到新增defer屬性的js會在文件解析之後,DOMContentLoaded觸發之前執行,也就是說,在新增defer屬性的js中我們是可以獲取到dom的
//js1延遲5s返回資料,console.log(document.getElementById('test'))
<script src="http://test.com/test/js1" defer></script>
<script>
console.log(document.getElementById('test'))
</script>
<div id="test"></div>
複製程式碼
內聯js,在id=“test”的dom之前,所以輸出null,而defer屬性的js列印出了dom
null
<div id="test"></div>
複製程式碼
即在defer屬性的js中,可以安全的操作dom
結論
- 如果css在js前面,則css的載入會阻塞js的執行,即css載入完畢後,執行js
- 如果css在js後面,則不會阻塞js的執行
- 非非同步js按照順序進行執行
- 多個async屬性的js,誰先載入完成誰執行
- defer屬性的js在文件解析之後,DOMContentLoaded觸發之前執行,多個defer屬性的js按文件順序執行
DOMContentLoaded和onload
網上檢視一些資料,一般說法是這樣的
1、當 onload 事件觸發時,頁面上所有的DOM,樣式表,指令碼,圖片,flash都已經載入完成了。
2、當 DOMContentLoaded事件觸發時,僅當DOM載入完成,不包括樣式表,圖片,flash。
但是通過測試發現並非如此,假設css延遲3s返回,圖片延遲5s返回,我們看看列印
//css延遲3s返回資料
<link rel="stylesheet" href="http://test.com/test/css1">
<script>
document.addEventListener('DOMContentLoaded',function () {
console.log('DOMContentLoaded')
},false)
window.onload=function () {
console.log("onload")
}
</script>
//延遲5s返回資料
<img src="http://3w.com/test/img1"/>
複製程式碼
可以看出3s之後才列印DOMContentLoaded,5s之後列印onload,也就是說DOMContentLoaded是需要等待css載入完成的 我們剛才前面說了,defer屬性的js要在DOMContentLoaded之前執行,那麼假如defer屬性延遲3s返回呢,我們看看效果
<script>
document.addEventListener('DOMContentLoaded',function () {
console.log('DOMContentLoaded')
},false)
window.onload=function () {
console.log("onload")
}
</script>
//js1延遲3s返回資料
<script src="http://test.com/test/js1" defer></script>
//延遲5s返回資料
<img src="http://3w.com/test/img1"/>
複製程式碼
同樣的也在3s之後列印DOMContentLoaded,5s之後列印onload,可以說網上的結論和實際是不相符的
結論
- 當 頁面上所有的DOM,樣式表,指令碼,圖片,flash都載入完成之後觸發onload。
- 當DOM載入解析完成、css載入完成、內聯js執行完成、defer屬性的js完成才會觸發DOMContentLoaded,圖片和async屬性的js,不會阻止發DOMContentLoaded的載入。
同樣的這些結論也給我們做前端優化提供一些思路,比如給js新增async屬性、減少js及css大小、使用懶載入減少圖片請求,使其儘快進入onload事件等等
有興趣的同學歡迎關注公眾號,讓我們一起重學前端,夯實基礎。