摘要:所以,拋開這些歧義和陷阱,我的問題變成了標簽的位置會影響首屏時間么然而答案并不是那么顯而易見,這得從瀏覽器的渲染機制說起。
說明:
本文提到的瀏覽器均是指Chrome。
“script標簽“指的都是普通的不帶其他屬性的外聯(lián)javascript。
web性能優(yōu)化的手段并不是非黑即白的,有些手段過頭了反而降低性能,所以在討論條件和結(jié)論的時候,雖然很多條件本身會帶來其他細微的負面或正面影響,為了不使論述失去重點,不會擴展太開。
一、從一個面試題說起面試前端的時候我喜歡問一些看上去是常識的問題。比如:為什么大家普遍把這樣的代碼放在body最底部?(為了溝通效率,我會提前和對方約定所有的討論都以chrome為例)
應聘者一般會回答:因為瀏覽器生成Dom樹的時候是一行一行讀HTML代碼的,script標簽放在最后面就不會影響前面的頁面的渲染。
我很雞賊地接著問:既然Dom樹完全生成好后頁面才能渲染出來,瀏覽器又必須讀完全部HTML才能生成完整的Dom樹,script標簽不放在body底部是不是也一樣?
留
一
段
空
白
讓
你
先
想
一
想
這其實是個開放性的問題,里面涉及的概念的界定本身就很重要。
“頁面渲染出來了” 指的是什么?嚴格來說,我的最后一問是有歧義的:我們需要統(tǒng)一一下什么叫我們經(jīng)常掛在嘴邊的“頁面渲染出來了” —— 指的是是 “首屏顯示出來了” 還是 “頁面完整地加載好了”(后面統(tǒng)稱StepC) ?
如果指的是首屏顯示出來了,那么問題又來了:假設(shè)網(wǎng)頁首屏有圖片,這里的“首屏” 指的是 “顯示了全部圖片的首屏”(后面統(tǒng)稱StepB) 還是 “沒有圖片的首屏”(后面統(tǒng)稱StepA)。
確定清楚 “頁面渲染出來了” 指的是 StepA、StepB、StepC 中的哪一個是非常關(guān)鍵的(雖然至今還沒有一個應聘者嘗試這么做過),如果 “頁面渲染出來了” 指的是 StepC,那么我的最后一問的答案是肯定的——script標簽不放在body底部不會拖慢頁面完整地加載好的時間。
顯然,我們往往更關(guān)心首屏時間,所以,如果 “頁面渲染出來了” 特指“沒有圖片的首屏”,那我的最后一問變成了下面這樣,又該如何回答呢?
陷阱既然Dom樹完全生成好后才能顯示“沒有圖片的首屏”,瀏覽器又必須讀完全部HTML才能生成完整的Dom樹,script標簽不放在body底部是不是也一樣?
然而上面的問題還是存在一個陷阱——既然Dom樹完全生成好后才能顯示“沒有圖片的首屏”這句話是帶欺騙性的,“沒有圖片的首屏”并不以“完整的Dom樹”為必要條件。也就是說:在生成Dom樹的過程中只要某些條件具備了,“沒有圖片的首屏”就能顯示出來。
所以,拋開這些歧義和陷阱,我的問題變成了:
script標簽的位置會影響首屏時間么?
然而答案并不是那么顯而易見,這得從瀏覽器的渲染機制說起。(再一次說明:本文所說的瀏覽器都是指chrome)
二、瀏覽器的渲染機制Google Web Fundamentals 是一個非常優(yōu)秀的文檔,里面講到了跟web、瀏覽器、前端的方方面面。我總結(jié)一下其中的 Ilya Grigorik 寫的 Critical rendering path 瀏覽器渲染機制部分的內(nèi)容如下:
幾個概念1、DOM:Document Object Model,瀏覽器將HTML解析成樹形的數(shù)據(jù)結(jié)構(gòu),簡稱DOM。
2、CSSOM:CSS Object Model,瀏覽器將CSS代碼解析成樹形的數(shù)據(jù)結(jié)構(gòu)。
3、DOM 和 CSSOM 都是以 Bytes → characters → tokens → nodes → object model. 這樣的方式生成最終的數(shù)據(jù)。如下圖所示:
DOM 樹的構(gòu)建過程是一個深度遍歷過程:當前節(jié)點的所有子節(jié)點都構(gòu)建好后才會去構(gòu)建當前節(jié)點的下一個兄弟節(jié)點。
4、Render Tree:DOM 和 CSSOM 合并后生成 Render Tree,如下圖:
Render Tree 和DOM一樣,以多叉樹的形式保存了每個節(jié)點的css屬性、節(jié)點本身屬性、以及節(jié)點的孩子節(jié)點。
注意:display:none 的節(jié)點不會被加入 Render Tree,而 visibility: hidden 則會,所以,如果某個節(jié)點最開始是不顯示的,設(shè)為 display:none 是更優(yōu)的。(具體可以看這里)
瀏覽器的渲染過程Create/Update DOM And request css/image/js:瀏覽器請求到HTML代碼后,在生成DOM的最開始階段(應該是 Bytes → characters 后),并行發(fā)起css、圖片、js的請求,無論他們是否在HEAD里。
注意:發(fā)起 js 文件的下載 request 并不需要 DOM 處理到那個 script 節(jié)點,比如:簡單的正則匹配就能做到這一點,雖然實際上并不一定是通過正則:)。這是很多人在理解渲染機制的時候存在的誤區(qū)。
Create/Update Render CSSOM:CSS文件下載完成,開始構(gòu)建CSSOM
Create/Update Render Tree:所有CSS文件下載完成,CSSOM構(gòu)建結(jié)束后,和 DOM 一起生成 Render Tree。
Layout:有了Render Tree,瀏覽器已經(jīng)能知道網(wǎng)頁中有哪些節(jié)點、各個節(jié)點的CSS定義以及他們的從屬關(guān)系。下一步操作稱之為Layout,顧名思義就是計算出每個節(jié)點在屏幕中的位置。
Painting:Layout后,瀏覽器已經(jīng)知道了哪些節(jié)點要顯示(which nodes are visible)、每個節(jié)點的CSS屬性是什么(their computed styles)、每個節(jié)點在屏幕中的位置是哪里(geometry)。就進入了最后一步:Painting,按照算出來的規(guī)則,通過顯卡,把內(nèi)容畫到屏幕上。
以上五個步驟前3個步驟之所有使用 “Create/Update” 是因為DOM、CSSOM、Render Tree都可能在第一次Painting后又被更新多次,比如JS修改了DOM或者CSS屬性。
Layout 和 Painting 也會被重復執(zhí)行,除了DOM、CSSOM更新的原因外,圖片下載完成后也需要調(diào)用Layout 和 Painting來更新網(wǎng)頁。
看 Timeline,一目了然我扒了一段有贊PC首頁的代碼到本地,通過Node跑起來。Node作為Server端,對/js/jquery.js 做了延時2s返回的處理,并且把 放到導航欄的下面,結(jié)果是這樣的:
從上面的Timeline我們可以看出:
首屏時間和DomContentLoad事件沒有必然的先后關(guān)系
所有CSS盡早加載是減少首屏時間的最關(guān)鍵
js的下載和執(zhí)行會阻塞Dom樹的構(gòu)建(嚴謹?shù)卣f是中斷了Dom樹的更新),所以script標簽放在首屏范圍內(nèi)的HTML代碼段里會截斷首屏的內(nèi)容。
script標簽放在body底部,做與不做async或者defer處理,都不會影響首屏時間,但影響DomContentLoad和load的時間,進而影響依賴他們的代碼的執(zhí)行的開始時間。
三、問題的答案回到前面的問題:
script標簽的位置會影響首屏時間么?
答案是:不影響(如果這里里的首屏指的是頁面從白板變成網(wǎng)頁畫面——也就是第一次Painting),但有可能截斷首屏的內(nèi)容,使其只顯示上面一部分。
為什么說是“有可能”呢?,如果該js下載地比css還快,或者script標簽不在第一屏的html里,實際上是不影響的。明白這一影響邊界非常重要,這樣我們在考察頁面性能瓶頸的時候就有的放矢了。舉個例子:在網(wǎng)頁的第二屏有一個通用模塊,實際上我們是可以把它的js邏輯獨立成一個文件,將模塊的html和js標簽放在一起做成獨立的模板引進來的(如果它的js比較小或者說因為多了一個文件會多占用一個TCP連接和帶寬,這實際上是另外一個話題了,請參考我文章開頭的聲明)。
四、總結(jié)、再進一步所以,總算弄清楚這個眾所周知的常識了。我們來總結(jié)一下:
如果script標簽的位置不在首屏范圍內(nèi),不影響首屏時間
所有的script標簽應該放在body底部是很有道理的
但從性能最優(yōu)的角度考慮,即使在body底部的script標簽也會拖慢首屏出來的速度,因為瀏覽器在最一開始就會請求它對應的js文件,而這,占用了有限的TCP鏈接數(shù)、帶寬甚至運行它所需要的CPU。這也是為什么script標簽會有async或defer屬性的原因之一。
可是,在復雜的實際應用場景中,要貫徹這幾條結(jié)論可能會遇到問題,比如:
你的頁面是分模塊來寫的,每一個模塊都有自己的html、js甚至css,當把這些模塊湊到一個頁面中的時候就會出現(xiàn)js自然而然地出現(xiàn)在HTML中間部分。你很難把script標簽都放到底部
即使你把script標簽都放到底部,但script標簽的存在終究是拖慢了首屏時間、DomContendLoad和loaded的時間。如果只有一個script標簽,我們可以加一個async,但多個async的script標簽的結(jié)果會是js文件被亂序執(zhí)行的,這顯然不是我們想要的。
我們也遇到了這樣的問題,所以就做了一個開源項目:Tiny-Loader —— A small loader that load CSS/JS in best way for page performance 簡單好用。
本文首發(fā)于我的
SegmentFault專欄:http://segmentfault.com/a/1190000004292479
個人技術(shù)博客:http://delai.me/code/js-and-performance/
轉(zhuǎn)載請注明出處
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/78470.html
摘要:雖然如此,但是網(wǎng)站前端性能優(yōu)化的思路基本沒變。為什么前端性能如此重要數(shù)據(jù)顯示只有的最終用戶響應時間花在了下載文檔上。前端性能優(yōu)化一味奉行最佳實踐有時候反而過而不及,所以針對項目的實際情況來優(yōu)化才是明智的選擇。 前端近幾年變化很大,各種工具,庫,框架并發(fā)。雖然如此,但是網(wǎng)站前端性能優(yōu)化的思路基本沒變。為什么前端性能如此重要?數(shù)據(jù)顯示: 只有 10%~20% 的最終用戶響應時間花在了下載...
摘要:錯過一周的優(yōu)質(zhì)內(nèi)容,不要再錯過周一的快速回顧寒潮來得出乎意料,手腳,一遍聽著史詩巨著張士超你到底把我家鑰匙放哪里了,一邊將優(yōu)質(zhì)內(nèi)容進行整理一周咨詢當技術(shù)成為一種武器翻譯組一周簡訊跟著輪子哥造輪子基本概念二排版前端名一前端遠程調(diào)試德來預加載 錯過一周的優(yōu)質(zhì)內(nèi)容,不要再錯過周一的快速回顧 寒潮來得出乎意料,dong手dong腳,一遍聽著史詩巨著《張士超你到底把我家鑰匙放哪里了》,一邊將優(yōu)質(zhì)...
摘要:錯過一周的優(yōu)質(zhì)內(nèi)容,不要再錯過周一的快速回顧寒潮來得出乎意料,手腳,一遍聽著史詩巨著張士超你到底把我家鑰匙放哪里了,一邊將優(yōu)質(zhì)內(nèi)容進行整理一周咨詢當技術(shù)成為一種武器翻譯組一周簡訊跟著輪子哥造輪子基本概念二排版前端名一前端遠程調(diào)試德來預加載 錯過一周的優(yōu)質(zhì)內(nèi)容,不要再錯過周一的快速回顧 寒潮來得出乎意料,dong手dong腳,一遍聽著史詩巨著《張士超你到底把我家鑰匙放哪里了》,一邊將優(yōu)質(zhì)...
閱讀 767·2023-04-25 15:13
閱讀 1388·2021-11-22 12:03
閱讀 816·2021-11-19 09:40
閱讀 1897·2021-11-17 09:38
閱讀 1702·2021-11-08 13:18
閱讀 649·2021-09-02 15:15
閱讀 1760·2019-08-30 15:54
閱讀 2623·2019-08-30 11:12