瀏覽器渲染頁面前需要先構(gòu)建 DOM 和 CSSOM 樹吊说。因此论咏,我們需要確保盡快將 HTML 和 CSS 都提供給瀏覽器。
TL;DR
- 字節(jié) → 字符 → 令牌 → 節(jié)點 → 對象模型颁井。
- HTML 標(biāo)記轉(zhuǎn)換成文檔對象模型 (DOM)厅贪;CSS 標(biāo)記轉(zhuǎn)換成 CSS 對象模型 (CSSOM)。
- DOM 和 CSSOM 是獨立的數(shù)據(jù)結(jié)構(gòu)雅宾。
- Chrome DevTools Timeline 讓我們可以捕獲和檢查 DOM 和 CSSOM 的構(gòu)建和處理開銷养涮。
文檔對象模型 (DOM)
<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>
讓我們從可能的最簡單情況入手:一個包含一些文本和一幅圖片的普通 HTML 頁面。瀏覽器如何處理此頁面?
- 轉(zhuǎn)換: 瀏覽器從磁盤或網(wǎng)絡(luò)讀取 HTML 的原始字節(jié)单寂,并根據(jù)文件的指定編碼(例如 UTF-8)將它們轉(zhuǎn)換成各個字符贬芥。
- 令牌化: 瀏覽器將字符串轉(zhuǎn)換成 W3C HTML5 標(biāo)準(zhǔn)規(guī)定的各種令牌吐辙,例如宣决,“<html>”、“<body>”昏苏,以及其他尖括號內(nèi)的字符串尊沸。每個令牌都具有特殊含義和一組規(guī)則。
- 詞法分析: 發(fā)出的令牌轉(zhuǎn)換成定義其屬性和規(guī)則的“對象”贤惯。
- DOM 構(gòu)建: 最后洼专,由于 HTML 標(biāo)記定義不同標(biāo)記之間的關(guān)系(一些標(biāo)記包含在其他標(biāo)記內(nèi)),創(chuàng)建的對象鏈接在一個樹數(shù)據(jù)結(jié)構(gòu)內(nèi)孵构,此結(jié)構(gòu)也會捕獲原始標(biāo)記中定義的父項-子項關(guān)系:HTML 對象是 body 對象的父項屁商,body 是 paragraph 對象的父項,依此類推颈墅。
整個流程的最終輸出是我們這個簡單頁面的文檔對象模型 (DOM)蜡镶,瀏覽器對頁面進(jìn)行的所有進(jìn)一步處理都會用到它。
瀏覽器每次處理 HTML 標(biāo)記時恤筛,都會完成以上所有步驟:將字節(jié)轉(zhuǎn)換成字符官还,確定令牌,將令牌轉(zhuǎn)換成節(jié)點毒坛,然后構(gòu)建 DOM 樹望伦。這整個流程可能需要一些時間才能完成,有大量 HTML 需要處理時更是如此煎殷。
如果您打開 Chrome DevTools 并在頁面加載時記錄時間線屯伞,就可以看到執(zhí)行該步驟實際花費的時間。在上例中豪直,將一堆 HTML 字節(jié)轉(zhuǎn)換成 DOM 樹大約需要 5 毫秒劣摇。對于較大的頁面,這一過程需要的時間可能會顯著增加顶伞。創(chuàng)建流暢動畫時饵撑,如果瀏覽器需要處理大量 HTML,這很容易成為瓶頸唆貌。
DOM 樹捕獲文檔標(biāo)記的屬性和關(guān)系滑潘,但并未告訴我們元素在渲染后呈現(xiàn)的外觀。那是 CSSOM 的責(zé)任锨咙。
CSS 對象模型 (CSSOM)
在瀏覽器構(gòu)建我們這個簡單頁面的 DOM 時语卤,在文檔的 head 部分遇到了一個 link 標(biāo)記,該標(biāo)記引用一個外部 CSS 樣式表:style.css。由于預(yù)見到需要利用該資源來渲染頁面粹舵,它會立即發(fā)出對該資源的請求钮孵,并返回以下內(nèi)容:
body { font-size: 16px }
p { font-weight: bold }
span { color: red }
p span { display: none }
img { float: right }
我們本可以直接在 HTML 標(biāo)記內(nèi)聲明樣式(內(nèi)聯(lián)),但讓 CSS 獨立于 HTML 有利于我們將內(nèi)容和設(shè)計作為獨立關(guān)注點進(jìn)行處理:設(shè)計人員負(fù)責(zé)處理 CSS眼滤,開發(fā)者側(cè)重于 HTML巴席,等等。
與處理 HTML 時一樣诅需,我們需要將收到的 CSS 規(guī)則轉(zhuǎn)換成某種瀏覽器能夠理解和處理的東西漾唉。因此,我們會重復(fù) HTML 過程堰塌,不過是為 CSS 而不是 HTML:
CSS 字節(jié)轉(zhuǎn)換成字符赵刑,接著轉(zhuǎn)換成令牌和節(jié)點,最后鏈接到一個稱為“CSS 對象模型”(CSSOM) 的樹結(jié)構(gòu)內(nèi):
CSSOM 為何具有樹結(jié)構(gòu)场刑?為頁面上的任何對象計算最后一組樣式時般此,瀏覽器都會先從適用于該節(jié)點的最通用規(guī)則開始(例如,如果該節(jié)點是 body 元素的子項牵现,則應(yīng)用所有 body 樣式)铐懊,然后通過應(yīng)用更具體的規(guī)則(即規(guī)則“向下級聯(lián)”)以遞歸方式優(yōu)化計算的樣式。
以上面的 CSSOM 樹為例進(jìn)行更具體的闡述施籍。span 標(biāo)記內(nèi)包含的任何置于 body 元素內(nèi)的文本都將具有 16 像素字號居扒,并且顏色為紅色 — font-size 指令從 body 向下級聯(lián)至 span。不過丑慎,如果某個 span 標(biāo)記是某個段落 (p) 標(biāo)記的子項喜喂,則其內(nèi)容將不會顯示。
還請注意竿裂,以上樹并非完整的 CSSOM 樹玉吁,它只顯示了我們決定在樣式表中替換的樣式。每個瀏覽器都提供一組默認(rèn)樣式(也稱為“User Agent 樣式”)腻异,即我們不提供任何自定義樣式時所看到的樣式进副,我們的樣式只是替換這些默認(rèn)樣式(例如默認(rèn) IE 樣式)。
要了解 CSS 處理所需的時間悔常,您可以在 DevTools 中記錄時間線并尋找“Recalculate Style”事件:與 DOM 解析不同影斑,該時間線不顯示單獨的“Parse CSS”條目,而是在這一個事件下一同捕獲解析和 CSSOM 樹構(gòu)建机打,以及計算的樣式的遞歸計算矫户。
我們的小樣式表需要大約 0.6 毫秒的處理時間,影響頁面上的 8 個元素 — 雖然不多残邀,但同樣會產(chǎn)生開銷皆辽。不過柑蛇,這 8 個元素從何而來呢?CSSOM 和 DOM 是獨立的數(shù)據(jù)結(jié)構(gòu)驱闷!結(jié)果證明耻台,瀏覽器隱藏了一個重要步驟。接下來空另,讓我們談一談將 DOM 與 CSSOM 關(guān)聯(lián)在一起的渲染樹盆耽。