JS一定要放在Body的最底部么?


JS一定要放在Body的最底部么?

一、從一個面試題說起

面試前端的時候我喜歡問一些看上去是常識的問題。比如:為什么大家普遍把 <script src=""></script> 這樣的代碼放在body最底部? (為了溝通效率,我會提前和對方約定所有的討論都以chrome為例)

應聘者一般會回答:因為瀏覽器生成Dom樹的時候是一行一行讀HTML代碼的,script標簽放在最后面就不會影響前面的頁面的渲染。

我很雞賊地接着問:既然Dom樹完全生成好后頁面才能渲染出來,瀏覽器又必須讀完全部HTML才能生成完整的Dom樹,script標簽不放在body底部是不是也一樣?

“頁面渲染出來了” 指的是什么?

嚴格來說,我的最后一問是有歧義的:我們需要統一一下什么叫我們經常掛在嘴邊的“頁面渲染出來了” —— 指的是是 “首屏顯示出來了” 還是 “頁面完整地加載好了”(后面統稱StepC) ?如果指的是首屏顯示出來了,那么問題又來了:假設網頁首屏有圖片,這里的“首屏” 指的是 “顯示了全部圖片的首屏”(后面統稱StepB) 還是 “沒有圖片的首屏”(后面統稱StepA)。

確定清楚 “頁面渲染出來了” 指的是 StepA、StepB、StepC 中的哪一個是非常關鍵的(雖然至今還沒有一個應聘者嘗試這么做過),如果 “頁面渲染出來了” 指的是 StepC,那么我的最后一問的答案是肯定的——script標簽不放在body底部不會拖慢頁面完整地加載好的時間。

顯然,我們往往更關心首屏時間,所以,如果 “頁面渲染出來了” 特指“沒有圖片的首屏”,那我的最后一問變成了下面這樣,又該如何回答呢?

既然Dom樹完全生成好后才能顯示“沒有圖片的首屏”,瀏覽器又必須讀完全部HTML才能生成完整的Dom樹,script標簽不放在body底部是不是也一樣?

陷阱

然而上面的問題還是存在一個陷阱—— 既然Dom樹完全生成好后才能顯示“沒有圖片的首屏” 這句話是帶欺騙性的,“沒有圖片的首屏”並不以“完整的Dom樹”為必要條件。也就是說: 在生成Dom樹的過程中只要某些條件具備了,“沒有圖片的首屏”就能顯示出來。

所以,拋開這些歧義和陷阱,我的問題變成了:

script標簽的位置會影響首屏時間么?

然而答案並不是那么顯而易見,這得從瀏覽器的渲染機制說起。 (再一次說明:本文所說的瀏覽器都是指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來更新網頁。

看Timeline,一目了然

我把扒了一段有贊PC首頁的代碼到本地,通過Node跑起來。Node作為Server端,對 /js/jquery.js 做了延時2s返回的處理,並且把 <script src="http://127.0.0.1:8080/js/jquery.js"></script> 放到導航欄的下面,結果是這樣的:

從上面的Timeline我們可以看出:

  • 首屏時間和DomContentLoad事件沒有必然的先后關系
  • 所有CSS盡早加載是減少首屏時間的最關鍵
  • js的下載和執行會阻塞Dom樹的構建,所以script標簽放在首屏范圍內的HTML代碼段里會可能影響首屏的內容。
  • 普通script標簽放在body底部,做與不做async或者defer處理,都不會影響首屏時間,但影響DomContentLoad和load的時間,進而影響依賴他們的代碼的執行的開始時間。

三、問題的答案

回到前面的問題:

script標簽的位置會影響首屏時間么?

答案是:不影響但有可能截斷首屏的內容,使其只顯示上面一部分

四、再進一步

所以,總算弄清楚這個眾所周知的常識了。但設計開發中可能會遇到難以把所有的js放到頁面最底部的情形。比如:你的頁面是分模塊來寫的,每一個模塊都有自己的html、js甚至css,當把這些模塊湊到一個頁面中的時候就會出現js自然而然地出現在HTML中間部分。

我們也遇到了這樣的問題,所以就做了一個開源項目: Tiny-Loader —— A small loader that load CSS/JS in best way for page performance 簡單好用。

回答下題目中所提到的問題,JS一定要放在Body的最底部么? 如果用了Tiny-Loader,JS可以不放在Body最底部。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



猜您在找 JS