發(fā)送 & 接收信息
數(shù)據(jù)是以“數(shù)據(jù)包”的形式通過互聯(lián)網(wǎng)發(fā)送,而數(shù)據(jù)包以字節(jié)為單位。當(dāng)你編寫一些 HTML、CSS 和 JS,并試圖在瀏覽器中打開 HTML 文件時(shí),瀏覽器會(huì)從你的硬盤(或網(wǎng)絡(luò))中讀取 HTML 的原始字節(jié)。明白了嗎?瀏覽器讀取的是原始數(shù)據(jù)字節(jié),而不是你編寫的代碼的實(shí)際字符。
讓我們繼續(xù)。瀏覽器接收字節(jié)數(shù)據(jù),但是,它用這些數(shù)據(jù)什么都做不了。數(shù)據(jù)的原始字節(jié)必須轉(zhuǎn)換為它所理解的形式,這是第一步。
從 HTML 的原始字節(jié)到 DOM
瀏覽器對(duì)象需要處理的是文檔對(duì)象模型(DOM)對(duì)象。那么,DOM 對(duì)象是從何而來的呢?這很簡(jiǎn)單。首先,將原始數(shù)據(jù)字節(jié)轉(zhuǎn)換為字符。這一點(diǎn),你可以通過你所編寫的代碼的字符看到。這種轉(zhuǎn)換是基于 HTML 文件的字符編碼完成的。至此,瀏覽器已經(jīng)從原始數(shù)據(jù)字節(jié)轉(zhuǎn)換為文件中的實(shí)際字符。但這不是最終的結(jié)果。這些字符會(huì)被進(jìn)一步解析為一些稱為“標(biāo)記(token)”的東西
那么,這些標(biāo)記是什么?文本文件中的一堆字符對(duì)瀏覽器引擎而言沒什么用處。如果沒有這個(gè)標(biāo)記化過程,那么這一堆堆字符只會(huì)生成一系列毫無意義的文本,即 HTML 代碼——不會(huì)生成一個(gè)真正的網(wǎng)站。
當(dāng)你保存一個(gè)擴(kuò)展名為.html 的文件時(shí),就向?yàn)g覽器引擎發(fā)出了把文件解析為 HTML 文檔的信號(hào)。瀏覽器“解釋”這個(gè)文件的方式是首先解析它。在解析過程中,特別是在標(biāo)記化過程中,瀏覽器會(huì)解析 HTML 文件中的每個(gè)開始和結(jié)束“標(biāo)簽(tag)”。解析器可以識(shí)別尖括號(hào)中的每個(gè)字符串,如“< html>”、“< p>”,也可以推斷出適用于其中任何一個(gè)字符串的規(guī)則集。例如,表示錨標(biāo)簽的標(biāo)記與表示段落標(biāo)簽的標(biāo)記具有不同的屬性。
從概念上講,你可以將標(biāo)記看作某種數(shù)據(jù)結(jié)構(gòu),它包含關(guān)于某個(gè) HTML 標(biāo)簽的信息。本質(zhì)上,HTML 文件會(huì)被分解成稱為標(biāo)記的小的解析單元。瀏覽器就是這樣開始識(shí)別你所編寫的內(nèi)容的。
但標(biāo)記還不是最終的結(jié)果。標(biāo)記化完成后,接下來,標(biāo)記將被轉(zhuǎn)換為節(jié)點(diǎn)。你可以將節(jié)點(diǎn)看作是具有特定屬性的不同對(duì)象。實(shí)際上,更好的解釋是,將節(jié)點(diǎn)看作是文檔對(duì)象樹中的獨(dú)立實(shí)體。但節(jié)點(diǎn)仍然不是最終結(jié)果。
現(xiàn)在,讓我們看一下最后一點(diǎn)。在創(chuàng)建好之后,這些節(jié)點(diǎn)將被鏈接到稱為 DOM 的樹數(shù)據(jù)結(jié)構(gòu)中。DOM 建立起了父子關(guān)系、相鄰兄弟關(guān)系等。在這個(gè) DOM 對(duì)象中,每個(gè)節(jié)點(diǎn)之間都建立了關(guān)系。現(xiàn)在,這是我們可以使用的東西了。
Bytes=> characters=>Tokens=>Node=>Dom
字節(jié)=>字符=>標(biāo)記=>節(jié)點(diǎn)=>Dom樹
根據(jù) HTML 文件的大小,DOM 的構(gòu)建過程可能需要一些時(shí)間。無論文件多小,它都需要一些時(shí)間。
CSS 如何轉(zhuǎn)換?
DOM 已經(jīng)創(chuàng)建。帶有一些 CSS 的典型 HTML 文件會(huì)包含下面這樣的樣式表鏈接:
<!DOCTYPE html>
<html>
<head>
? ? <link rel="stylesheet" type="text/css" media="screen" href="main.css" />
</head>
<body>
</body>
</html>
當(dāng)瀏覽器接收到原始數(shù)據(jù)字節(jié)并啟動(dòng) DOM 構(gòu)建過程時(shí),它還會(huì)發(fā)出請(qǐng)求來獲取鏈接的 main.css 樣式表。當(dāng)瀏覽器開始解析 HTML 時(shí),在找到 css 文件的鏈接標(biāo)簽的同時(shí),它會(huì)發(fā)出請(qǐng)求來獲取它。可能你已經(jīng)猜到,瀏覽器還是接收 CSS 數(shù)據(jù)的原始字節(jié),從互聯(lián)網(wǎng)或是本地磁盤。
但是,瀏覽器如何處理這些 CSS 數(shù)據(jù)的原始字節(jié)呢?
從 CSS 的原始字節(jié)到 CSSOM
當(dāng)瀏覽器接收到 CSS 的原始字節(jié)時(shí),會(huì)啟動(dòng)一個(gè)和處理 HTML 原始字節(jié)類似的過程。就是說,原始數(shù)據(jù)字節(jié)被轉(zhuǎn)換成字符,然后標(biāo)記,然后形成節(jié)點(diǎn),最后形成樹結(jié)構(gòu)。
什么是樹結(jié)構(gòu)?大多數(shù)人都知道 DOM 這個(gè)詞。同樣,也有一種 CSS 樹結(jié)構(gòu),稱為 CSS 對(duì)象模型,簡(jiǎn)稱為 CSSOM。
你知道,瀏覽器不能使用 HTML 或 CSS 的原始字節(jié)。必須將其轉(zhuǎn)換成它能識(shí)別的形式,也就是這些樹形結(jié)構(gòu)。
CSS 有一個(gè)叫做級(jí)聯(lián)的東西。級(jí)聯(lián)是瀏覽器確定如何在元素上應(yīng)用樣式的機(jī)制。
由于影響元素的樣式可能來自父元素,即通過繼承,或者已經(jīng)在元素本身設(shè)置,所以 CSSOM 樹結(jié)構(gòu)變得很重要。為什么?這是因?yàn)闉g覽器必須遞歸遍歷 CSS 樹結(jié)構(gòu)并確定影響特定元素的樣式。
一切順利。瀏覽器有了 DOM 和 CSSOM 對(duì)象。現(xiàn)在,我們能在屏幕上呈現(xiàn)一些東西了嗎?
渲染樹
我們現(xiàn)在得到的是兩個(gè)獨(dú)立的樹結(jié)構(gòu),它們似乎沒有共同的目標(biāo)。
DOM 和 CSSOM 樹結(jié)構(gòu)是兩個(gè)獨(dú)立的樹結(jié)構(gòu)。DOM 中包含關(guān)于頁面 HTML 元素關(guān)系的所有信息,而 CSSOM 則包含關(guān)于元素樣式的信息。好了,瀏覽器現(xiàn)在把 DOM 和 CSSOM 樹組合成一棵渲染樹。
DOM + CSSOM = 渲染樹
渲染樹包含頁面上所有關(guān)于可見 DOM 內(nèi)容的信息以及不同節(jié)點(diǎn)所需的所有 CSSOM 信息。注意,如果一個(gè)元素被 CSS 隱藏,例如使用 display; none,那么節(jié)點(diǎn)就不會(huì)包含在渲染樹中。隱藏元素會(huì)出現(xiàn)在 DOM 中,但不會(huì)出現(xiàn)在渲染樹中。這是因?yàn)殇秩緲浣Y(jié)合了來自 DOM 和 CSSOM 的信息,所以它知道不能把隱藏元素包含在樹中。
構(gòu)建好渲染樹之后,瀏覽器將繼續(xù)下個(gè)步驟:布局!
布局
現(xiàn)在,我們有了屏幕上的內(nèi)容和所有可見內(nèi)容的樣式信息——但是我們并沒有實(shí)際在屏幕上渲染任何內(nèi)容。首先,瀏覽器必須計(jì)算頁面上每個(gè)對(duì)象的確切大小和位置。這就好比是,把要在頁面上渲染的所有元素的內(nèi)容和樣式信息傳遞給一個(gè)有才華的數(shù)學(xué)家。然后,這位數(shù)學(xué)家用瀏覽器的視窗計(jì)算出每個(gè)元素的確切位置和大小。
這個(gè)布局步驟考慮了從 DOM 和 CSSOM 接收到的內(nèi)容和樣式,并執(zhí)行了所有必要的布局計(jì)算。有時(shí),你會(huì)聽到人們把這個(gè)“布局”階段稱為“回流(reflow)”
藝術(shù)家出場(chǎng)
現(xiàn)在,每個(gè)元素的確切位置已經(jīng)計(jì)算出來,剩下的就是將元素“繪制”到屏幕上。
考慮一下。我們已經(jīng)得到了在屏幕上顯示元素所需的所有信息。我們只要把它展示給用戶。這就是這個(gè)階段的全部工作。有了元素內(nèi)容(DOM)、樣式(CSSOM)和計(jì)算得出的元素的精確布局信息,瀏覽器現(xiàn)在就可以將節(jié)點(diǎn)逐個(gè)“繪制”到屏幕上了。元素現(xiàn)在終于呈現(xiàn)在屏幕上了!
渲染阻塞資源
當(dāng)你聽到“渲染阻塞(render-blocking)”時(shí),你會(huì)想到什么?我猜你想的是,“有東西阻止了屏幕上節(jié)點(diǎn)的實(shí)際繪制”。如果你這么說,那你說的完全正確!
優(yōu)化網(wǎng)站的第一準(zhǔn)則是讓最重要的 HTML 和 CSS 盡可能快地傳遞到客戶端。在成功繪制之前,必須構(gòu)造 DOM 和 CSSOM,因此,HTML 和 CSS 都是渲染阻塞資源。關(guān)鍵是,你應(yīng)該盡快將 HTML 和 CSS 提供給客戶端,以優(yōu)化應(yīng)用程序的首次渲染時(shí)間。
JavaScript 如何執(zhí)行?
一個(gè)好的 Web 應(yīng)用程序肯定會(huì)使用一些 JavaScript。這是一定的。JavaScript 的“問題”在于你可以使用 JavaScript 修改頁面的內(nèi)容和樣式。通過這種方式,你可以從 DOM 樹中刪除元素和添加元素,還可以通過 JavaScript 修改元素的 CSSOM 屬性。
<!DOCTYPE html>
<html>
<head>
? ? <meta name="viewport" content="width=device-width,initial-scale=1">
? ? <title>Medium Article Demo</title>
? ? <link rel="stylesheet" href="style.css">
</head>
<body>
? ? <p id="header">How Browser Rendering Works</p>
? ? <div><img src="https://i.imgur.com/jDq3k3r.jpg"></div>
</body>
</html>
這是一個(gè)非常簡(jiǎn)單的文檔。樣式表 style.css 只有下面一個(gè)聲明語句:
body {
? background: #8cacea;
}
根據(jù)前面的解釋,瀏覽器從磁盤(或網(wǎng)絡(luò))讀取 HTML 文件的原始字節(jié)并將其轉(zhuǎn)換為字符。字符被進(jìn)一步解析為標(biāo)記。當(dāng)解析器遇到< link rel="stylesheet" href="style.css">時(shí),就會(huì)請(qǐng)求獲取 CSS 文件 style.css。DOM 構(gòu)造繼續(xù)進(jìn)行,當(dāng) CSS 文件返回一些內(nèi)容后,CSSOM 構(gòu)造就開始了。
引入 JavaScript 后,這個(gè)過程會(huì)發(fā)生什么變化?要記住,其中最重要的一件事情是,每當(dāng)瀏覽器遇到腳本標(biāo)簽時(shí),DOM 構(gòu)造就會(huì)暫停!整個(gè) DOM 構(gòu)建過程都將停止,直到腳本執(zhí)行完成。
這是因?yàn)?JavaScript 可以同時(shí)修改 DOM 和 CSSOM。由于瀏覽器不確定特定的 JavaScript 會(huì)做什么,所以它采取的預(yù)防措施是停止整個(gè) DOM 構(gòu)造。
<!DOCTYPE html>
<html>
<head>
? ? <meta name="viewport" content="width=device-width,initial-scale=1">
? ? <title>Medium Article Demo</title>
? ? <link rel="stylesheet" href="style.css">
</head>
<body>
? ? <p id="header">How Browser Rendering Works</p>
? ? <div><img src="https://i.imgur.com/jDq3k3r.jpg"></div>
? ? <script>
? ? ? ? let header = document.getElementById("header");
? ? ? ? console.log("header is: ", header);
? ? </script>
</body>
</html>
在腳本標(biāo)簽中,我將訪問 id 為 header 的 DOM 節(jié)點(diǎn),然后將其輸出到控制臺(tái)。可以正常運(yùn)行。
但是,你是否注意到,這個(gè)腳本標(biāo)簽位于 body 標(biāo)簽的底部?讓我們把它放在 head 中,看看會(huì)發(fā)生什么:一旦我這樣做,header 就解析為 null。
為什么會(huì)這樣?很簡(jiǎn)單。當(dāng) HTML 解析器正在構(gòu)建 DOM 時(shí),發(fā)現(xiàn)了一個(gè)腳本標(biāo)簽。此時(shí),body 標(biāo)簽及其所有內(nèi)容還沒有被解析。DOM 構(gòu)造將停止,直到腳本執(zhí)行完成:
當(dāng)腳本試圖訪問一個(gè) id 為 header 的 DOM 節(jié)點(diǎn)時(shí),由于 DOM 還沒有完成對(duì)文檔的解析,所以它還不存在。這把我們帶到了另一個(gè)重要的問題。腳本的位置很重要。
這還不是全部。如果你將內(nèi)聯(lián)腳本提取到外部本地文件 app.js 中,行為是一樣的。DOM 的構(gòu)建仍然會(huì)停止:
<!DOCTYPE html>
<html>
<head>
? ? <meta name="viewport" content="width=device-width,initial-scale=1">
? ? <title>Medium Article Demo</title>
? ? <link rel="stylesheet" href="style.css">
? ? <script src="app.js"></script>
</head>
<body>
? ? <p id="header">How Browser Rendering Works</p>
? ? <div><img src="https://i.imgur.com/jDq3k3r.jpg"></div>
</body>
</html>
那么,如果 app.js 不是本地的而必須通過互聯(lián)網(wǎng)獲取呢?如果網(wǎng)速很慢,需要數(shù)千毫秒來獲取 app.js,DOM 的構(gòu)建也會(huì)暫停幾千毫秒!!!這是一個(gè)很大的性能問題,而且還不止于此。JavaScript 還可以訪問 CSSOM 并對(duì)其進(jìn)行修改。例如,這是有效的 JavaScript 語句:
document.getElementsByTagName("body")[0].style.backgroundColor = "red";
那么,當(dāng)解析器遇到一個(gè)腳本標(biāo)簽而 CSSOM 還沒有準(zhǔn)備好時(shí),會(huì)發(fā)生什么情況呢?答案很簡(jiǎn)單。Javascript 執(zhí)行將會(huì)停止,直到 CSSOM 就緒。因此,雖然 DOM 構(gòu)造在遇到腳本標(biāo)簽時(shí)會(huì)停止,但 CSSOM 不會(huì)發(fā)生這種情況。對(duì)于 CSSOM,JS 執(zhí)行會(huì)等待。沒有 CSSOM,就沒有 JS 執(zhí)行。
async 屬性
在默認(rèn)情況下,每個(gè)腳本都是一個(gè)解析器阻斷器!DOM 的構(gòu)建總是會(huì)被打斷。不過,有一種方法可以改變這種默認(rèn)行為。如果將 async 關(guān)鍵字添加到腳本標(biāo)簽中,那么 DOM 構(gòu)造就不會(huì)停止。DOM 構(gòu)造將繼續(xù),腳本將在下載完成并準(zhǔn)備就緒后執(zhí)行。
<!DOCTYPE html>
<html>
<head>
? ? <meta name="viewport" content="width=device-width,initial-scale=1">
? ? <title>Medium Article Demo</title>
? ? <link rel="stylesheet" href="style.css">
? ? <script src="https://some-link-to-app.js" async></script>
</head>
<body>
? ? <p id="header">How Browser Rendering Works</p>
? ? <div><img src="https://i.imgur.com/jDq3k3r.jpg"></div>
</body>
</html>
關(guān)鍵渲染路徑
目前為止,我們討論了從接收 HTML、CSS 和 JS 字節(jié)到將它們轉(zhuǎn)換為屏幕上的像素之間的所有步驟。這整個(gè)過程稱為關(guān)鍵渲染路徑。優(yōu)化網(wǎng)站性能就是優(yōu)化關(guān)鍵渲染路徑。
一個(gè)經(jīng)過良好優(yōu)化的站點(diǎn)應(yīng)該能夠漸進(jìn)式渲染,而不是讓整個(gè)過程受阻。
這是 Web 應(yīng)用程序慢或快的區(qū)別所在。周密的關(guān)鍵渲染路徑(CRP)優(yōu)化策略使瀏覽器能夠通過確定優(yōu)先加載的資源以及資源加載的順序來盡可能快地加載頁面。
常見引起回流屬性和方法
任何會(huì)改變?cè)貛缀涡畔?元素的位置和尺寸大小)的操作,都會(huì)觸發(fā)回流,
添加或者刪除可見的DOM元素;
元素尺寸改變——邊距、填充、邊框、寬度和高度
內(nèi)容變化,比如用戶在input框中輸入文字
瀏覽器窗口尺寸改變——resize事件發(fā)生時(shí)
計(jì)算 offsetWidth 和 offsetHeight 屬性
設(shè)置 style 屬性的值
常見引起重繪屬性和方法
下面例子中,觸發(fā)了幾次回流和重繪?
var s = document.body.style;
s.padding = "2px"; // 回流+重繪
s.border = "1px solid red"; // 再一次 回流+重繪
s.color = "blue"; // 再一次重繪
s.backgroundColor = "#ccc"; // 再一次 重繪
s.fontSize = "14px"; // 再一次 回流+重繪
// 添加node,再一次 回流+重繪
document.body.appendChild(document.createTextNode('abc!'));
如何減少回流、重繪
使用 transform 替代 top
使用 visibility 替換 display: none ,因?yàn)榍罢咧粫?huì)引起重繪,后者會(huì)引發(fā)回流(改變了布局)
不要把節(jié)點(diǎn)的屬性值放在一個(gè)循環(huán)里當(dāng)成循環(huán)里的變量。
不要使用 table 布局,可能很小的一個(gè)小改動(dòng)會(huì)造成整個(gè) table 的重新布局
動(dòng)畫實(shí)現(xiàn)的速度的選擇,動(dòng)畫速度越快,回流次數(shù)越多,也可以選擇使用 requestAnimationFrame
CSS 選擇符從右往左匹配查找,避免節(jié)點(diǎn)層級(jí)過多
將頻繁重繪或者回流的節(jié)點(diǎn)設(shè)置為圖層,圖層能夠阻止該節(jié)點(diǎn)的渲染行為影響別的節(jié)點(diǎn)。比如對(duì)于 video 標(biāo)簽來說,瀏覽器會(huì)自動(dòng)將該節(jié)點(diǎn)變?yōu)閳D層。
async和defer的作用是什么?有什么區(qū)別?
接下來我們對(duì)比下 defer 和 async 屬性的區(qū)別:
(1)情況1<script src="script.js"></script>
沒有 defer 或 async,瀏覽器會(huì)立即加載并執(zhí)行指定的腳本,也就是說不等待后續(xù)載入的文檔元素,讀到就加載并執(zhí)行。
(2)情況2<script async src="script.js"></script> (異步下載)
async 屬性表示異步執(zhí)行引入的 JavaScript,與 defer 的區(qū)別在于,如果已經(jīng)加載好,就會(huì)開始執(zhí)行——無論此刻是 HTML 解析階段還是 DOMContentLoaded 觸發(fā)之后。需要注意的是,這種方式加載的 JavaScript 依然會(huì)阻塞 load 事件。換句話說,async-script 可能在 DOMContentLoaded 觸發(fā)之前或之后執(zhí)行,但一定在 load 觸發(fā)之前執(zhí)行。
(3)情況3 <script defer src="script.js"></script>(延遲執(zhí)行)
defer 屬性表示延遲執(zhí)行引入的 JavaScript,即這段 JavaScript 加載時(shí) HTML 并未停止解析,這兩個(gè)過程是并行的。整個(gè) document 解析完畢且 defer-script 也加載完成之后(這兩件事情的順序無關(guān)),會(huì)執(zhí)行所有由 defer-script 加載的 JavaScript 代碼,然后觸發(fā) DOMContentLoaded 事件。
defer 與相比普通 script,有兩點(diǎn)區(qū)別:載入 JavaScript 文件時(shí)不阻塞 HTML 的解析,執(zhí)行階段被放到 HTML 標(biāo)簽解析完成之后。在加載多個(gè)JS腳本的時(shí)候,async是無順序的加載,而defer是有順序的加載。
為什么操作 DOM 慢
因?yàn)?DOM 是屬于渲染引擎中的東西,而 JS 又是 JS 引擎中的東西。當(dāng)我們通過 JS 操作 DOM 的時(shí)候,其實(shí)這個(gè)操作涉及到了兩個(gè)線程之間的通信,那么勢(shì)必會(huì)帶來一些性能上的損耗。操作 DOM 次數(shù)一多,也就等同于一直在進(jìn)行線程之間的通信,并且操作 DOM 可能還會(huì)帶來重繪回流的情況,所以也就導(dǎo)致了性能上的問題。
渲染頁面時(shí)常見哪些不良現(xiàn)象?
由于瀏覽器的渲染機(jī)制不同,在渲染頁面時(shí)會(huì)出現(xiàn)兩種常見的不良現(xiàn)象----白屏問題和FOUS(無樣式內(nèi)容閃爍)
FOUC:由于瀏覽器渲染機(jī)制(比如firefox),再CSS加載之前,先呈現(xiàn)了HTML,就會(huì)導(dǎo)致展示出無樣式內(nèi)容,然后樣式突然呈現(xiàn)的現(xiàn)象;
白屏:有些瀏覽器渲染機(jī)制(比如chrome)要先構(gòu)建DOM樹和CSSOM樹,構(gòu)建完成后再進(jìn)行渲染,如果CSS部分放在HTML尾部,由于CSS未加載完成,瀏覽器遲遲未渲染,從而導(dǎo)致白屏;也可能是把js文件放在頭部,腳本會(huì)阻塞后面內(nèi)容的呈現(xiàn),腳本會(huì)阻塞其后組件的下載,出現(xiàn)白屏問題。
總結(jié)
瀏覽器工作流程:構(gòu)建DOM -> 構(gòu)建CSSOM -> 構(gòu)建渲染樹 -> 布局 -> 繪制。
當(dāng)瀏覽器接收到原始數(shù)據(jù)字節(jié)并啟動(dòng) DOM 構(gòu)建過程時(shí),它還會(huì)發(fā)出請(qǐng)求來獲取鏈接的 main.css 樣式表,啟動(dòng)CSSOM構(gòu)建
構(gòu)建DOM的過程中,不是等所有Token都轉(zhuǎn)換完成后再去生成節(jié)點(diǎn)對(duì)象,而是一邊生成Token一邊消耗Token來生成節(jié)點(diǎn)對(duì)象。換句話說,每個(gè)Token被生成后,會(huì)立刻消耗這個(gè)Token創(chuàng)建出節(jié)點(diǎn)對(duì)象
DOM 和 CSSOM 樹結(jié)構(gòu)是兩個(gè)獨(dú)立的樹結(jié)構(gòu)。DOM 中包含關(guān)于頁面 HTML 元素關(guān)系的所有信息,而 CSSOM 則包含關(guān)于元素樣式的信息。
瀏覽器得遞歸 CSSOM 樹,然后確定具體的元素到底是什么樣式,注意:CSS匹配HTML元素是一個(gè)相當(dāng)復(fù)雜和有性能問題的事情。所以,DOM樹要小,CSS盡量用id和class,千萬不要過渡層疊下去
CSSOM會(huì)阻塞渲染,只有當(dāng)CSSOM構(gòu)建完畢后才會(huì)進(jìn)入下一個(gè)階段構(gòu)建渲染樹。
渲染樹包含頁面上所有關(guān)于可見 DOM 內(nèi)容的信息以及不同節(jié)點(diǎn)所需的所有 CSSOM 信息,隱藏元素會(huì)出現(xiàn)在 DOM 中,但不會(huì)出現(xiàn)在渲染樹中。這是因?yàn)殇秩緲浣Y(jié)合了來自 DOM 和 CSSOM 的信息,所以它知道不能把隱藏元素包含在樹中。
構(gòu)建好渲染樹之后,瀏覽器必須計(jì)算頁面上每個(gè)對(duì)象的確切大小和位置,這個(gè)布局步驟考慮了從 DOM 和 CSSOM 接收到的內(nèi)容和樣式,并執(zhí)行了所有必要的布局計(jì)算(回流或者自動(dòng)重排)。
每當(dāng)瀏覽器遇到腳本標(biāo)簽時(shí),DOM 構(gòu)造就會(huì)暫停!整個(gè) DOM 構(gòu)建過程都將停止,但 CSSOM 不會(huì)發(fā)生這種情況,直到腳本執(zhí)行完成,當(dāng)解析器遇到一個(gè)腳本標(biāo)簽而 CSSOM 還沒有準(zhǔn)備好時(shí),Javascript 執(zhí)行將會(huì)停止,直到 CSSOM 就緒,對(duì)于 CSSOM,JS 執(zhí)行會(huì)等待。
通常情況下DOM和CSSOM是并行構(gòu)建的,但是當(dāng)瀏覽器遇到一個(gè)script標(biāo)簽時(shí),DOM構(gòu)建將暫停,直至腳本完成執(zhí)行。但由于JavaScript可以修改CSSOM,所以需要等CSSOM構(gòu)建完畢后再執(zhí)行JS。
JS文件不只是阻塞DOM的構(gòu)建,它會(huì)導(dǎo)致CSSOM也阻塞DOM的構(gòu)建。原本DOM和CSSOM的構(gòu)建是互不影響,井水不犯河水,但是一旦引入了JavaScript,CSSOM也開始阻塞DOM的構(gòu)建,只有CSSOM構(gòu)建完畢后,DOM再恢復(fù)DOM構(gòu)建。在這種情況下,瀏覽器會(huì)先下載和構(gòu)建CSSOM,然后再執(zhí)行JavaScript,最后在繼續(xù)構(gòu)建DOM。
如果你想首屏渲染的越快,就越不應(yīng)該在首屏就加載 JS 文件,建議將 script 標(biāo)簽放在 body 標(biāo)簽底部。
重繪:當(dāng)render tree中的一些元素需要更新屬性,而這些屬性只是影響元素的外觀、風(fēng)格,而不會(huì)影響布局的,比如background-color。
回流:當(dāng)render tree中的一部分(或全部)因?yàn)樵氐囊?guī)模尺寸、布局、隱藏等改變而需要重新構(gòu)建
回流必定會(huì)發(fā)生重繪,重繪不一定會(huì)引發(fā)回流。重繪和回流會(huì)在我們?cè)O(shè)置節(jié)點(diǎn)樣式時(shí)頻繁出現(xiàn),同時(shí)也會(huì)很大程度上影響性能。回流所需的成本比重繪高的多,改變父節(jié)點(diǎn)里的子節(jié)點(diǎn)很可能會(huì)導(dǎo)致父節(jié)點(diǎn)的一系列回流。