從我接觸前端到如今,一直聽到的一句話:操做DOM的成本很高,不要輕易去操做DOM。尤爲是React、vue等MV*框架的出現,數據驅動視圖的模式愈加深刻人心,jQuery時代提供的強大便利地操做DOM的API在前端工程裏用的愈來愈少。刨根問底,這裏說的成本,到底高在哪兒呢?
Document Object Model 文檔對象模型
什麼是DOM?可能不少人第一反應就是div、p、span等html標籤(至少我是),但要知道,DOM是Model,是Object Model,對象模型,是爲HTML(and XML)提供的API。HTML(Hyper Text Markup Language)是一種標記語言,HTML在DOM的模型標準中被視爲對象,DOM只提供編程接口,卻沒法實際操做HTML裏面的內容。但在瀏覽器端,前端們能夠用腳本語言(JavaScript)經過DOM去操做HTML內容。css
那麼問題來了,只有JavaScript才能調用DOM這個API嗎?html
答案是NO。前端
Python也能夠訪問DOM。因此DOM不是提供給Javascript的API,也不是Javascript裏的API。vue
PS: 實質上還存在CSSOM:CSS Object Model,瀏覽器將CSS代碼解析成樹形的數據結構,與DOM是兩個獨立的數據結構。node
討論DOM操做成本,確定要先了解該成本的來源,那麼就離不開瀏覽器渲染。
這裏暫只討論瀏覽器拿到HTML以後開始解析、渲染。(怎麼拿到HTML資源的可能後續另開篇總結吧,什麼握握握手啊揮揮揮揮手啊,萬惡的flag...)git
<html> <head> <meta name="viewport" content="width=device-width,initial-scale=1"> <link href="style.css" rel="stylesheet"> <title>Critical Path</title> </head> <body> <p>Hello <span>web performance</span> students!</p> <div><img src="awesome-photo.jpg"></div> </body> </html>
不管是DOM仍是CSSOM,都是要通過
Bytes → characters → tokens → nodes → object model
這個過程。
DOM樹構建過程:當前節點的全部子節點都構建好後纔會去構建當前節點的下一個兄弟節點。
上述也提到了CSSOM的構建過程,也是樹的結構,在最終計算各個節點的樣式時,瀏覽器都會先從該節點的廣泛屬性(好比body裏設置的全局樣式)開始,再去應用該節點的具體屬性。還有要注意的是,每一個瀏覽器都有本身默認的樣式表,所以不少時候這棵CSSOM樹只是對這張默認樣式表的部分替換。github
DOM樹和CSSOM樹合併生成render樹
簡單描述這個過程:web
DOM樹從根節點開始遍歷可見節點,這裏之因此強調了「可見」,是由於若是遇到設置了相似display: none;
的不可見節點,在render過程當中是會被跳過的(但visibility: hidden; opacity: 0
這種仍舊佔據空間的節點不會被跳過render),保存各個節點的樣式信息及其他節點的從屬關係。chrome
有了各個節點的樣式信息和屬性,但不知道各個節點的確切位置和大小,因此要經過佈局將樣式信息和屬性轉換爲實際可視窗口的相對大小和位置。編程
萬事俱備,最後只要將肯定好位置大小的各節點,經過GPU渲染到屏幕的實際像素。
reflow(迴流): 根據Render Tree佈局(幾何屬性),意味着元素的內容、結構、位置或尺寸發生了變化,須要從新計算樣式和渲染樹;
repaint(重繪): 意味着元素髮生的改變隻影響了節點的一些樣式(背景色,邊框顏色,文字顏色等),只須要應用新樣式繪製這個元素就能夠了;
reflow迴流的成本開銷要高於repaint重繪,一個節點的迴流每每回致使子節點以及同級節點的迴流;
GoogleChromeLabs 裏面有一個csstriggers,列出了各個CSS屬性對瀏覽器執行Layout、Paint、Composite的影響。
現代瀏覽器會對迴流作優化,它會等到足夠數量的變化發生,再作一次批處理迴流。
display: none
,操做完再顯示。(由於隱藏元素不在render樹內,所以修改隱藏元素不會觸發迴流重繪)構建Render樹須要DOM和CSSOM,因此HTML和CSS都會阻塞渲染。因此須要讓CSS儘早加載(如:放在頭部),以縮短首次渲染的時間。
阻塞瀏覽器的解析,也就是說發現一個外鏈腳本時,需等待腳本下載完成並執行後纔會繼續解析HTML
普通的腳本會阻塞瀏覽器解析,加上defer或async屬性,腳本就變成異步,可等到解析完畢再執行
<html> <head> <meta name="viewport" content="width=device-width,initial-scale=1"> <link href="style.css" rel="stylesheet"> </head> <body> <p>Hello <span>web performance</span> students!</p> <div><img src="awesome-photo.jpg"></div> <script src="app.js"></script> </body> </html>
script
標記,喚醒JavaScript解析器
,就會進行暫停 blocked
瀏覽器解析HTML,並等到 CSSOM
構建完畢,才執行js腳本說了這麼多,其實能夠總結幾點瀏覽器首屏渲染優化的方向
其實寫了這麼多,感受偏題了,大量的資料參考的是chrome開發者文檔。感受js腳本資源那塊仍是有點亂,包括和DOMContentLoaded的關係,但願你們能多多指點,多多批評,謝謝大佬們。
操做DOM具體的成本,說究竟是形成瀏覽器迴流reflow和重繪reflow,從而消耗GPU資源。
https://developers.google.com/web/fundamentals/performance/critical-rendering-path/
已同步至我的博客- 軟硬皆施
Github 歡迎star :)