重學前端:做了這麼多年前端,你真的瞭解html中資源載入執行時序嗎?

蟄伏已久發表於2019-03-08

做了好幾年前端了,最近想進個大廠,參加了阿里、百度、今日頭條的面試,被徹底的打擊了,感覺自己的基礎不是很牢,所以萌發了重學前端的想法,向習以為常的知識/技能,多問一個為什麼,多瞭解一下原理,或者原始碼

今天我們來聊聊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

重學前端:做了這麼多年前端,你真的瞭解html中資源載入執行時序嗎?

而如果我們把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

重學前端:做了這麼多年前端,你真的瞭解html中資源載入執行時序嗎?

接下來在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個圖片

重學前端:做了這麼多年前端,你真的瞭解html中資源載入執行時序嗎?

瀏覽器每次只能載入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個檔案

重學前端:做了這麼多年前端,你真的瞭解html中資源載入執行時序嗎?

結論

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前面的原因呢?

重學前端:做了這麼多年前端,你真的瞭解html中資源載入執行時序嗎?

我們調換一下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">
複製程式碼

結論

  1. 如果css在js前面,則css的載入會阻塞js的執行,即css載入完畢後,執行js
  2. 如果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會按照文件順序執行

重學前端:做了這麼多年前端,你真的瞭解html中資源載入執行時序嗎?

上面提到新增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

結論

  1. 如果css在js前面,則css的載入會阻塞js的執行,即css載入完畢後,執行js
  2. 如果css在js後面,則不會阻塞js的執行
  3. 非非同步js按照順序進行執行
  4. 多個async屬性的js,誰先載入完成誰執行
  5. 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,可以說網上的結論和實際是不相符的

結論

  1. 當 頁面上所有的DOM,樣式表,指令碼,圖片,flash都載入完成之後觸發onload。
  2. 當DOM載入解析完成、css載入完成、內聯js執行完成、defer屬性的js完成才會觸發DOMContentLoaded,圖片和async屬性的js,不會阻止發DOMContentLoaded的載入。

同樣的這些結論也給我們做前端優化提供一些思路,比如給js新增async屬性、減少js及css大小、使用懶載入減少圖片請求,使其儘快進入onload事件等等

有興趣的同學歡迎關注公眾號,讓我們一起重學前端,夯實基礎。

重學前端:做了這麼多年前端,你真的瞭解html中資源載入執行時序嗎?

相關文章