本文僅是技術驗證,記錄,交流,不針對任何人。有冒犯的地方,請諒解。 該文首發於https://vsnail.cn/static/doc/blog/browserPerformance.html
最近看了一篇文章《JS一定要放在Body的最底部麼?聊聊瀏覽器的渲染機制》.從一道面試題說起,然後剖析了瀏覽器的載入和渲染機制.
為什麼大家普遍把這樣的程式碼放在body最底部?(為了溝通效率,我會提前和對方約定所有的討論都以chrome為例)
在沒有看過文章之前,估計我會回答為了是加快瀏覽器對頁面的渲染.但是是哪個步驟,哪個時刻加速了就不得而知了.看過了這篇文章,大致瞭解了瀏覽器的載入和渲染機制.但是對於文中的有些說法百思不得其解,因此在某些問題上以實驗的形式加以驗證,故而對實驗結果的總結對錯與否估計也不能下個定論,只能權當參考.
瀏覽器的渲染機制
幾個概念
-
1、DOM:Document Object Model,瀏覽器將HTML解析成樹形的資料結構,簡稱DOM。
-
2、CSSOM:CSS Object Model,瀏覽器將CSS程式碼解析成樹形的資料結構。
-
3、DOM 和 CSSOM 都是以 Bytes → characters → tokens → nodes → object model. 這樣的方式生成最終的資料。如下圖所示:
上圖是從大神那拷貝過來的,視覺中國哥哥,千萬別來找我哦。我就是前端打雜一名,沒錢的
DOM樹的構建過程是一個深度遍歷過程:當前節點的所有子節點都構建好後才會去構建當前節點的下一個兄弟節點。
- 4、Render Tree:DOM 和 CSSOM 合併後生成 Render Tree,如下圖:
上圖是從大神那拷貝過來的,視覺中國哥哥,千萬別來找我哦。我就是前端打雜一名,沒錢的
Render Tree
和DOM
一樣,以多叉樹的形式儲存了每個節點的css
屬性、節點本身屬性、以及節點的孩子節點。
注意:display:none
的節點不會被加入Render Tree
,而visibility: hidden
則會,所以,如果某個節點最開始是不顯示的,設為display:none
是更優的。具體可以看這裡
瀏覽器的渲染過程
- 1、Create/Update DOM And request css/image/js:
瀏覽器請求到HTML
程式碼後,在生成DOM
的最開始階段(應該是 Bytes
→ characters
後),並行發起css
、圖片、js
的請求,無論他們是否在HEAD
裡。
注意:發起js
檔案的下載request
並不需要DOM
處理到那個script
節點,比如:簡單的正則匹配就能做到這一點,雖然實際上並不一定是通過正則。這是很多人在理解渲染機制的時候存在的誤區
- 2、
Create/Update Render CSSOM
:
CSS
檔案下載完成,開始構建CSSOM
- 3、
Create/Update Render Tree
:
所有CSS
檔案下載完成,CSSOM
構建結束後,和 DOM
一起生成 Render Tree
。
- 4、Layout:
有了Render Tree
,瀏覽器已經能知道網頁中有哪些節點、各個節點的CSS定義以及他們的從屬關係。下一步操作稱之為Layout
,顧名思義就是計算出每個節點在螢幕中的位置。
- 5、Painting:
Layout
後,瀏覽器已經知道了哪些節點要顯示(which nodes are visible)、每個節點的CSS屬性是什麼(their computed styles)、每個節點在螢幕中的位置是哪裡(geometry)。就進入了最後一步:Painting,按照算出來的規則,通過顯示卡,把內容畫到螢幕上。
以上五個步驟前3個步驟之所有使用 “Create/Update
” 是因為DOM
、CSSOM
、Render Tree
都可能在第一次Painting
後又被更新多次,比如JS
修改了DOM
或者CSS
屬性。
Layout
和 Painting
也會被重複執行,除了DOM
、CSSOM
更新的原因外,圖片下載完成後也需要呼叫Layout
和 Painting
來更新網頁。
以上都是《JS一定要放在Body的最底部麼?聊聊瀏覽器的渲染機制》歸納總結出來的,並且文中一直都有提到"首屏"這個詞,為了一致,本文中的"首屏"也是同一個意思,即,“沒有圖片的首屏”.
再來回到之前的面試問題,"script標籤的位置會影響首屏時間麼?"
其實通過上面的瀏覽器渲染過程的描述可以得知,其實script
的位置不會影響首屏的時間,但有可能截斷首屏的內容,使其只顯示上面一部分。
並且《JS一定要放在Body的最底部麼?聊聊瀏覽器的渲染機制》文中也總結了以下幾點:
- 如果script標籤的位置不在首屏範圍內,不影響首屏時間
- 所有的script標籤應該放在body底部是很有道理的
- 但從效能最優的角度考慮,即使在body底部的script標籤也會拖慢首屏出來的速度,因為瀏覽器在最一開始就會請求它對應的js檔案,而這,佔用了有限的TCP連結數、頻寬甚至執行它所需要的CPU。這也是為什麼script標籤會有async或defer屬性的原因之一。
疑惑和注意點
對於瀏覽器渲染過程有一定的瞭解了,但是還是有些疑惑以及注意點.
1. 第一次渲染耗時與script位置是無關的
通過對瀏覽器渲染過程的瞭解,我們可以發現其實瀏覽器第一次做Painting
,是在Render tree
構建完成後,layout
計算完成以後進行的.那麼可以理解為,如果(我是說如果,當然估計這種情況會很少)您要載入的js
不會去改變dom
的結構或樣式.那麼其實script
標籤放在什麼位置並不會影響到瀏覽器的渲染耗時.
我們可以看看下面的例子.我們寫了一個簡單的web
服務,使用node.js
做後臺,實際上後臺僅僅只是一個靜態資源伺服器而已.我們在這個服務上面動了一點點的手腳,即,如果是請求bootstrap.min.js
,那麼我們將等待2秒後才返回,其他資源正常返回.
例子工程地址:github.com/btshj-snail…
同時,我們建立一個簡單的html介面,這個介面引入了三個css,三個js.這個介面程式碼如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>Document</title>
<link rel="stylesheet" href="./style/bootstrap.min.css" />
<link rel="stylesheet" href="./style/bootstrap-theme.min.css" />
<link rel="stylesheet" href="./style/main.css" />
</head>
<body>
<div>
<p id="first" class="beautiful"></p>
<p>人之初 性本善 性相近 習相遠</p>
<p>苟不教 性乃遷 教之道 貴以專</p>
昔孟母 擇鄰處 子不學 斷機杼<br />
<img src="./img/1000.jpeg" alt="" style="width:200px;height:200px;margin:20px;"/>
<p id="content"></p>
</div>
<script src="./js/jquery-2.1.1.min.js"></script>
<script src="./js/bootstrap.min.js"></script>
<script src="./js/main.js"></script>
</body>
</html>
複製程式碼
main.js檔案中僅有一個簡單的邏輯處理,即在id為content的元素中插入一些話而已.
//main.js
document.querySelector("#content").innerHTML = "請注意,請注意,重點不是美女....重點是你看到這串文字..這才是老夫的真實用意..這句話是通過js動態追加的.當你看到這句話的時候,表明這個js檔案已經執行了...其實在你不知道的時候,我偷偷的將另外一個js延遲載入了..."
複製程式碼
OK,一切都準備就緒了,那麼我們來看看當我們延遲2秒返回bootstrap.min.js
檔案,會對main.js
造成什麼樣的影響.
從github
上clone
下來工程,然後進入工程目錄,輸入命令node ./server/app.js
,然後使用chrome訪問http://localhost:9001/static/index.html
.我們接著使用F12
,開啟chrome
的開發者工具,點選performance
,我們可以輕鬆的獲取瀏覽器對該介面的渲染過程.
我們可以從圖中可以看出,在93.6ms
的時候,瀏覽器就已經開始paint
了.而在2048ms
時,才接受到bootstrap.min.js
的資料.因此第一次渲染耗時與script
位置是無關的.
2. 瀏覽器是嚴格按照script標籤的順序來載入的嗎?
其實這個答案在不瞭解瀏覽器渲染過程的情況下,也知道這是肯定的.否則無法保證程式碼的依賴性.在很早以前,我們開發的時候,通常會將jquery
的檔案放在第一個引用,其他依賴jquery
庫的js
檔案放在其後面,這就是為了保證後面js
檔案使用jquery
時,jquery
已經被載入.通過這個,我們也可以得知瀏覽器是嚴格按照script
標籤的順序來載入js
檔案的.
OK,我們依然用上面的例子來驗證.上面的列子中我們引用main.js
檔案的script
標籤是放在bootstrap.min.js
的script
標籤之後的,並且在後臺,會延遲2s
才返回bootstrap.min.js
,那麼此時main.js
上面時候返回,瀏覽器什麼時候開始解析執行main.js
?
從上圖我們可以得知,其實第一次paint時,其實是沒有渲染出美女圖片下方的文字的.那麼也就是說,其實第一次paint
時,瀏覽器是沒有執行main.js
.那麼有可能你會說,也許接收到main.js
檔案,實在paint
之後呢.其實這個說法,也是完全有可能的,但是呢,結論先不說,嘿嘿..我們先來看看chrome
的performance
的渲染過程,用事實說話,嘿嘿.
上面按照時間順序標了4條記錄.
第一條,是瀏覽器接收到main.js
資料;
第二條,是瀏覽器第一次開始paint
第三條,執行bootstrap.min.js
檔案.
第四條,執行main.js
檔案.
那麼完全可以看出,下載js檔案是"併發"的.而執行js
檔案,則是上一個檔案執行完後,才執行下一個的.那麼為了加快最終渲染效果,可以將不重要或者是第一次無需使用的js
檔案,放在所有script
的後面載入.也可以給這些script
標籤加上defer
或async
參考文獻
1、《JS一定要放在Body的最底部麼?聊聊瀏覽器的渲染機制》delai.me/code/js-and…
2、案例工程(exp-browserPainting) github.com/btshj-snail…