瀏覽器渲染流程以下圖所示:css
圖片來源:www.html5rocks.com/en/tutorial…html
大概能夠劃分紅如下幾個步驟:html5
解析的過程分爲兩個步驟:詞法分析和語法分析。
詞法分析負責將輸入內容分解成一個個有效標記;而語法分析負責根據語言的語法規則分析文檔的結構,從而構建解析樹。經過詞法分析能夠將無關的字符(好比空格和換行符)分離出來。web
解析是一個迭代的過程。一般,解析器會向詞法分析器請求一個新標記,並嘗試將其與某條語法規則進行匹配。若是發現了匹配規則,解析器會將一個對應於該標記的節點添加到解析樹中,而後繼續請求下一個標記。算法
若是沒有規則能夠匹配,解析器就會將標記存儲到內部,並繼續請求標記,直至找到可與全部內部存儲的標記匹配的規則。若是找不到任何匹配規則,解析器就會引起一個異常。這意味着文檔無效,包含語法錯誤。瀏覽器
不少時候,解析樹還不是最終產品。解析一般是在轉譯過程當中使用的,而轉譯是指將輸入文檔轉換成另外一種格式。編譯就是這樣一個例子。編譯器可將源代碼編譯成機器代碼,具體過程是首先將源代碼解析成解析樹,而後將解析樹翻譯成機器代碼文檔。緩存
解析器的輸出「解析樹」是由 DOM 元素和屬性節點構成的樹結構。DOM 是文檔對象模型 (Document Object Model) 的縮寫。它是 HTML 文檔的對象表示,同時也是外部內容(例如 JavaScript)與 HTML 元素之間的接口。
解析樹的根節點是「Document」對象。markdown
DOM 與標記之間幾乎是一一對應的關係。好比下面這段標記:
網絡
<html>
<body>
<p>
Hello World
</p>
<div> <img src="example.png"/></div>
</body>
</html>
複製代碼
可翻譯成以下的 DOM 樹:框架
HTML5 規範詳細地描述瞭解析算法。此算法由兩個階段組成:標記化和樹構建。
標記化是詞法分析過程,將輸入內容解析成多個標記。HTML 標記包括起始標記、結束標記、屬性名稱和屬性值。
標記生成器識別標記,傳遞給樹構造器,而後接受下一個字符以識別下一個標記;如此反覆直到輸入的結束。
和 HTML 不一樣,CSS 是上下文無關的語法。事實上,CSS 規範定義了 CSS 的詞法和語法。
WebKit 使用 Flex 和 Bison 解析器生成器,經過 CSS 語法文件自動建立解析器。Bison 會建立自下而上的移位歸約解析器。Firefox 使用的是人工編寫的自上而下的解析器。這兩種解析器都會將 CSS 文件解析成 StyleSheet 對象,且每一個對象都包含 CSS 規則。CSS 規則對象則包含選擇器和聲明對象,以及其餘與 CSS 語法對應的對象。
網絡的模型是同步的。網頁解析器遇到 <script>
標記時文檔的解析將中止,直到腳本執行完畢。若是腳本是外部的,那麼解析過程會中止,直到從網絡同步抓取資源完成後再繼續。你能夠在<script>
標籤上添加「defer」屬性(<script defer>
),這樣它就不會中止文檔解析,而是等到解析結束才執行。HTML5 增長了一個async屬性,可將腳本標記爲異步<script async>
),以便由其餘線程解析和執行。
WebKit 和 Firefox 都進行了這項優化。在執行腳本時,其餘線程會解析文檔的其他部分,找出並加載須要經過網絡加載的其餘資源。經過這種方式,資源能夠在並行鏈接上加載,從而提升整體速度。請注意,預解析器不會修改 DOM 樹,而是將這項工做交由主解析器處理;預解析器只會解析外部資源(例如外部腳本、樣式表和圖片)的引用。
另外一方面,樣式表有着不一樣的模型。理論上來講,應用樣式表不會更改 DOM 樹,所以彷佛沒有必要等待樣式表並中止文檔解析。但這涉及到一個問題,就是腳本在文檔解析階段會請求樣式信息。若是當時尚未加載和解析樣式,腳本就會得到錯誤的回覆,這樣顯然會產生不少問題。這看上去是一個非典型案例,但事實上很是廣泛。Firefox 在樣式表加載和解析的過程當中,會禁止全部腳本。而對於 WebKit 而言,僅當腳本嘗試訪問的樣式屬性可能受還沒有加載的樣式表影響時,它纔會禁止該腳本。
Render tree是由 DOM 和 CSSOM 組合構建而成的。也是頁面可視化元素按照其顯示順序而組成的樹,是文檔的可視化表示。它的做用是讓瀏覽器按照正確的順序繪製內容。
Firefox 將Render tree中的元素稱爲「框架」。WebKit 使用的術語是呈現器或呈現對象。
呈現器知道如何佈局並將自身及其子元素繪製出來。
呈現器是和 DOM 元素相對應的,但並不是一一對應。非可視化的 DOM 元素不會插入呈現樹中,例如「head」元素。若是元素的 display 屬性值爲「none」,那麼也不會顯示在呈現樹中(可是 visibility 屬性值爲「hidden」的元素仍會顯示)。
有一些 DOM 元素對應多個可視化對象。它們每每是具備複雜結構的元素,沒法用單一的矩形來描述。例如,「select」元素有 3 個呈現器:一個用於顯示區域,一個用於下拉列表框,還有一個用於按鈕。若是因爲寬度不夠,文本沒法在一行中顯示而分爲多行,那麼新的行也會做爲新的呈現器而添加。
另外一個關於多呈現器的例子是格式無效的 HTML。根據 CSS 規範,inline 元素只能包含 block 元素或 inline 元素中的一種。若是出現了混合內容,則應建立匿名的 block 呈現器,以包裹 inline 元素。
有一些呈現對象對應於 DOM 節點,但在樹中所在的位置與 DOM 節點不一樣。浮動定位和絕對定位的元素就是這樣,它們處於正常的流程以外,放置在樹中的其餘地方,並映射到真正的框架,而放在原位的是佔位框架。
當Render Tree剛構建完時,並不包含元素節點的位置和大小信息。計算這些值的過程稱爲佈局或重排。
HTML 採用基於流的佈局模型,這意味着大多數狀況下只要一次遍歷就能計算出幾何信息。處於流中靠後位置元素一般不會影響靠前位置元素的幾何特徵,所以佈局能夠按從左至右、從上至下的順序遍歷文檔。可是也有例外狀況,好比 HTML 表格的計算就須要不止一次的遍歷。
座標系是相對於根框架而創建的,使用的是上座標和左座標。
佈局是一個遞歸的過程。它從根呈現器(對應於 HTML 文檔的 元素)開始,而後遞歸遍歷部分或全部的框架層次結構,爲每個須要計算的呈現器計算幾何信息。
根呈現器的位置左邊是 0,0,其尺寸爲視口(也就是瀏覽器窗口的可見區域)。
全部的呈現器都有一個「layout」或者「reflow」方法,每個呈現器都會調用其須要進行佈局的子代的 layout 方法。
爲避免對全部細小更改都進行總體佈局,瀏覽器採用了一種「dirty 位」系統。若是某個呈現器發生了更改,或者將自身及其子代標註爲「dirty」,則須要進行佈局。
有兩種標記:「dirty」和「children are dirty」。「children are dirty」表示儘管呈現器自身沒有變化,但它至少有一個子代須要佈局。
全局佈局是指觸發了整個呈現樹範圍的佈局,觸發緣由可能包括:
佈局能夠採用增量方式,也就是隻對 dirty 呈現器進行佈局(這樣可能存在須要進行額外佈局的弊端)。
當呈現器爲 dirty 時,會異步觸發增量佈局。例如,當來自網絡的額外內容添加到 DOM 樹以後,新的呈現器附加到了呈現樹中。
在繪製階段,系統會遍歷呈現樹,並調用呈現器的「paint」方法,將呈現器的內容顯示在屏幕上。繪製工做是使用用戶界面基礎組件完成的。
和佈局同樣,繪製也分爲全局(繪製整個呈現樹)和增量兩種。在增量繪製中,部分呈現器發生了更改,可是不會影響整個樹。更改後的呈現器將其在屏幕上對應的矩形區域設爲無效,這致使 OS 將其視爲一塊「dirty 區域」,並生成「paint」事件。OS 會很巧妙地將多個區域合併成一個。在 Chrome 瀏覽器中,狀況要更復雜一些,由於 Chrome 瀏覽器的呈現器不在主進程上。Chrome 瀏覽器會在某種程度上模擬 OS 的行爲。展現層會偵聽這些事件,並將消息委託給呈現根節點。而後遍歷呈現樹,直到找到相關的呈現器,該呈現器會從新繪製本身(一般也包括其子代)。
CSS2 規範定義了繪製流程的順序。繪製的順序其實就是元素進入堆棧樣式上下文的順序。這些堆棧會從後往前繪製,所以這樣的順序會影響繪製。塊呈現器的堆棧順序以下:
在從新繪製以前,WebKit 會將原來的矩形另存爲一張位圖(Bitmap),而後只繪製新舊矩形之間的差別部分。
在發生變化時,瀏覽器會盡量作出最小的響應。所以,元素的顏色改變後,只會對該元素進行重繪。元素的位置改變後,只會對該元素及其子元素(可能還有同級元素)進行佈局和重繪。添加 DOM 節點後,會對該節點進行佈局和重繪。一些重大變化(例如增大「html」元素的字體)會致使緩存無效,使得整個呈現樹都會進行從新佈局和繪製。
參考連接