解析HTML
當瀏覽器向服務器發(fā)出請求獲得頁面時遭商,服務器用二進制流返回一個HTML船殉,響應頭Content-Type=text/html; charset=UTF-8
如果不帶這個請求頭,瀏覽器會直接展示HTML文本,將如下代碼顯示在頁面中
<!DOCTYPE HTML>
<html>
<head>
<title>Rendering Test</title>
<link rel="stylesheet" href="./style.css"/>
</head>
<body>
<div class="container">
<h1>Hello World!</h1>
<p>This is a sample paragraph.</p>
</div>
<script src="./main.js"></script>
</body>
</html>
構建DOM樹
瀏覽器獲得html
后秤朗,開始解讀這個代碼,它為每一個html
標簽創(chuàng)建Javascript Node
笔喉。不過每一個標簽都有不同的屬性取视,所以它們的Class
也不一樣,比如div使用HTMLDivElement
創(chuàng)建
var divEle = document.querySelector('div.container');
divEle instanceof HTMLDivElement // true
divEle instanceof Node // true
Chrome提供DOMParserAPI用來構建DOM樹
const dom = new DOMParser();
dom.parseFromString('<div><h1>123</h1></div>', 'text/html'); // #document
DOM (Document Object Model)
以一個h標簽為例常挚,它的原型鏈大概是這個樣子:
h1 -> HTMLHeadingElement -> HTMLElement -> Element -> Node -> EventTarget -> Object
Javascript 本身不理解什么是DOM作谭,DOM是瀏覽器提供的web api,用來渲染頁面并且暴露給開發(fā)者用來操作DOM奄毡。
最終相互嵌套的html標簽被解讀成DOM樹折欠。
構建CSSOM樹
CSSOM (CSS Object Model),構建好DOM樹之后,瀏覽器從各處加載CSS資源(內(nèi)置锐秦,外部咪奖,user-agent,內(nèi)聯(lián))酱床,然后構建CSSOM樹赡艰,CSSOM樹的結構大致與DOM樹一致,但它不包含不會打印在頁面上的標簽斤葱。
構建Render樹
渲染樹是DOM樹和CSSOM樹的組合慷垮,它會排除掉那些不占地方的元素,比如display:none 和 長寬都為0的元素揍堕。
Layout & Paint
現(xiàn)在瀏覽器構建好了渲染樹料身,它就開始在頁面上繪制這些元素了。瀏覽器首先計算每個元素的大小和位置(以像素為單位)衩茸,這個過程是reflow(回流或者重排)芹血。然后是繪制 (Paint),本質(zhì)上就是填充像素的過程楞慈,包括繪制文字幔烛、顏色、圖像囊蓝、邊框和陰影等饿悬,也就是一個DOM元素所有的可視效果。一般來說聚霜,這個繪制過程是在多個層上完成的狡恬。最后是渲染層合并(Composite),將所有層按照合理的順序合并成一個圖層蝎宇。
到目前為止弟劲,構建DOM樹,CSSOM樹姥芥,處理渲染邏輯都是渲染引擎做的兔乞。這里列舉了一些市面上常見的渲染引擎。
name | browser | css suffix |
---|---|---|
Trident | IE | -ms |
WebKit | Safari | -webkit |
Blink | Chrome Edge Opera | -webkit |
Gecko | Firefox | -moz |
Blink | Edge | -ms |
DOM Parser Block
一旦瀏覽器在解析html標簽時遇到外部資源凉唐,比如一個script file <script src="url"></script>
庸追,一個樣式表<link rel="stylesheet" href="url"/>
或者一張圖片
<img src="url" />
,瀏覽器都會開始下載這些資源熊榛。
我們都知道锚国,解析DOM樹是在主線程上做的,如果主線程忙碌玄坦,DOM解析也暫停血筑。只有script會阻塞渲染绘沉。假如是內(nèi)嵌腳本,那么主線程會暫停解析DOM豺总,開始執(zhí)行腳本车伞;如果是外部文件,主線程同樣會暫停解析DOM喻喳,直至文件下載成功并且執(zhí)行完畢后才會繼續(xù)解析DOM另玖。至于為什么瀏覽器要這么做,因為瀏覽器將操作DOM的API暴露給了Javascript表伦,萬一腳本中有大量的操作DOM的邏輯谦去,等待DOM構建完畢再執(zhí)行,只會帶來更大的性能問題蹦哼。
但我們也知道鳄哭,很多時候這種阻塞是無意義的,比如腳本中如果沒有操作DOM的邏輯纲熏,此時妆丘,我們可以使用async屬性,帶有async的script標簽局劲,下載不會暫停主線程勺拣,但腳本一旦下載完畢,仍然需要占據(jù)主線程鱼填,阻塞渲染药有。我們還可以使用defer屬性,帶有這個屬性的script標簽不僅下載不會暫停主線程剔氏,下載之后也不需要立刻執(zhí)行塑猖,所有的defer腳本都是DOM樹構建好之后再執(zhí)行的竹祷,執(zhí)行順序組照他們在DOM樹中出現(xiàn)的順序谈跛。
很多時候我們說script阻塞了渲染(render-blocking,其實本質(zhì)上是阻塞了解析(parser-blocking)塑陵,因為阻塞了解析感憾,才阻塞了渲染。
剛才我們說css資源不是parser- blocking令花,只能說它沒有直接導致parser blocked阻桅,但某種情況確實會block parser。
當瀏覽器遇到外部內(nèi)聯(lián)樣式文件兼都,它首先會靜默下載嫂沉,此時DOM parsing不會被阻塞。但渲染樹的構建 (critical rendering path CRP) 會被暫停(后續(xù)的內(nèi)容暫時不會顯示在頁面上扮碧,但并不意味著頁面無內(nèi)容趟章,當瀏覽器加載到部分HTML字節(jié)流時即開始構建DOM樹杏糙,然后構建渲染樹,顯示在頁面上)蚓土。
假設我們的外部樣式標簽沒有寫在head里宏侍,就會出現(xiàn)頁面的樣式一直在更換的情況,我們稱之為Flash of Unstyled Content (FOUC)蜀漆,因此我們建議盡早加載所有的樣式文件谅河。
當正在加載樣式文件時遇到script標簽會發(fā)生什么呢?首先script標簽直接阻塞了DOM parser确丢,script標簽是否可以執(zhí)行呢绷耍?答案是不能,因為此時script標簽拿不到最新的樣式鲜侥,因此樣式文件加載會阻塞script execution锨天,間接的阻塞了DOM parser。
如何讓外部樣式表不阻塞render呢剃毒,可以加上media='non-render-blocking'
Loaded
+ DOMContentLoaded DCL事件意味著瀏覽器已經(jīng)完成了完整的DOM樹構建(意味著腳本執(zhí)行完畢病袄,CSSOM樹構建完成)。
+ load 其他資源文件也全部加載完畢
瀏覽器首屏會出現(xiàn)的問題
- 過長的加載時間
- 無格式頁面閃現(xiàn)或者樣式發(fā)生變化(FOUC)
- 加載不必要的資源