記得前幾個月小伙伴准備跳槽的時候,建議他可以准備下“從輸入URL到頁面加載發生了什么”這個前端面試常見問題。結果真的命中了兩次。后來仔細思考,中間的確也反應出了很多知識點。如果能把整個過程弄懂,並且熟悉里面的所有知識,也的確能算一個中級前端工程師了。看了下網上的最詳細版答案,發現自己主要不明白的地方在於渲染過程這一塊。
以前理解的前端渲染(非異步的情況),是在服務器返回首屏內容后,瀏覽器從上到下邊解析邊渲染整個response,其中如果為mvvm前端框架進行渲染,則會先將首屏渲染完成,然后執行js代碼,交由框架進行渲染(這里具體又分為虛擬dom和雙向綁定等,本次不談及這塊)。但后來深入了解后,才發現細節遠不止這些。
前端渲染流程圖
正如上面圖片顯示的流程一樣,瀏覽器拿到全部代碼之后,並不會直接從上到下進行渲染,而是將html和css抽分開來進行渲染成dom樹(html結構樹)和樣式結構體(css結構樹),等兩個都渲染完成,再一並合成帶樣式的dom樹,然后再根據樹的具體內容,繪制成頁面進行顯示。這其中涉及到了前端首屏加載優化和重繪回流等問題。
前端首屏加載優化
在寫頁面的時候,大多都是將css放在head里面,而將js丟到body的最下面,原因是不要讓js阻塞整個頁面的首屏渲染,讓白屏時間不要太長。因此大量數據的頁面,如今大多通過異步加載的形式進行優化。其原理是通過先出現個大概輪廓,顯示一個加載中的圖標,讓用戶有耐心等待下去。甚至包括現如今較為火熱的移動端和app,也出了骨架屏這一概念(通過先渲染出大概輪廓,然后再慢慢顯示數據,首先渲染出來的大概輪廓最近被稱為骨架屏)。其原理也是白屏時間不宜太長。
但其實當初看到這個優化辦法的時候,自己內心一直有個疑問,為什么只把js丟下面,css不能也丟下去,先渲染出無樣式的頁面,再等待頁面加載,這樣不更能縮短白屏時間嗎?而這個問題的答案就在上面的渲染流程中。頁面渲染必定會先經過渲染css,即使放在下方,css也會阻塞整個頁面的渲染。為此我做了個實驗,將css阻塞2s再進行返回,然后將css置於body底部進行加載,整個頁面果然白屏了2s。當然,針對這一問題,網上已經有很多解決方案,比如通過改變載入的link標簽的media屬性(當media屬性不符合當前的時候,css會加載,但不會阻塞到當前頁面,所以當css加載完畢后觸發其onload事件改寫media即可,不過經社區有人測試發現在ie和ff中會失敗),以及開源項目loadCSS(原理相近,基本都是通過修改media或者js添加link標簽進行解決)等。
理解了整個渲染流程,其實對於渲染優化便能更得心應手了。無論是懶加載、異步加載、預加載等一系列名詞,其實無非也就是針對首屏、白屏等時間進行優化。
重繪回流
在涉及到瀏覽器渲染這一問題中,不得不提及的便是重繪回流的問題。
這兩個問題也應該歸於渲染優化,回流和重繪分別對應的便是前端渲染流程中的結合dom(html結構樹)和cssom(css結構樹)成為一個帶樣式的dom結構,以及將這一結構樹渲染成我們所看到的頁面這兩個步驟。而在初次渲染的時候,這些操作都是一步完成。但在用戶與web頁面進行交互的時候,如果出現了對dom結構的操作(刪除或修改),也會出發回流,即重新構建dom結構,然后再重繪。而如果單純只是修改樣式,則會產生重繪。
因此,如果用戶在操作過程中涉及到大量的修改dom操作,即會觸發多次回流。為了優化性能,一般的做法是盡量減少非必要的觸發回流的行為(如重復獲取style信息),而針對不可避免的操作,則可以盡量放在一起。瀏覽器針對這一問題有個優化,會維護一個隊列,將所有引起回流和重繪的操作放在里面,達到一定數量或間隔再一次性執行。
除此以外,react中的虛擬dom概念其實也有助於減少回流。react中維護了一套與真實dom相同的虛擬dom。如果有涉及重繪回流的操作,會先在虛擬dom中完成,再一次性改變到真實dom中。
文章首發於個人博客——啊昊的咖啡屋
