「瀏覽器工做原理:現代網絡瀏覽器幕後揭祕」文章分析

1、概述

上一篇文章中對一些CSS問題進行回答,發現不少都是知其然不知其因此然,而寫文章的目的就是改變知其然不知其因此然的狀態(由於這樣的簡歷,人家不要我),而閱讀了tali garsiel的一篇關於瀏覽器工做原理的中文版,以爲文章不是很適合XX閱讀,讀起來比較生澀,因此花了差很少XX小時整理了這篇文章。css

參考文章: 瀏覽器的工做原理:新式網絡瀏覽器幕後揭祕html

做者注:
英文原文:howbrowserswork前端

瀏覽器工做流程
  1. 經過網絡引擎獲取到文檔;html5

  2. 開始解析文檔,在遇到腳本時當即執行腳本,文檔中止解析,若是是外部腳本則等待資源加載並執行完成,外部腳本可經過加defer或者async告訴瀏覽器異步處理,這樣不會打斷解析;面試

  3. 佈局,爲每一個節點分配一個應出如今屏幕上的確切座標;算法

  4. 繪製,呈現引擎會遍歷呈現樹,由用戶界面後端層將每一個節點繪製出來;chrome

  5. 顯示,值得注意的是這一步並不會等到文檔解析完成,會將部分已解析的文檔儘快顯示。後端

做者注:不少現代瀏覽器都有進行預解析優化,即主解析器解析DOM樹,使用其餘線程解析外部資源的引用。瀏覽器

做者注:爲何會中止解析?由於腳本在文檔解析階段會請求樣式信息。若是當時尚未加載和解析樣式,腳本就會得到錯誤的回覆,這樣顯然會產生不少問題。微信

呈現引擎工做流程

呈現引擎從網絡層獲取文檔數據,擁有數據後的呈現引擎開始解析HTML文檔,並將各標記逐個轉化爲內容樹上的DOM節點。同時也會解析外部CSS 文件以及樣式元素中的樣式數據,HTML 中這些帶有視覺指令的樣式信息將用於建立另外一個樹結構將建立另外一個樹結構:呈現樹。

呈現樹構建完畢以後,進入「佈局」處理階段,也就是爲每一個節點分配一個應出如今屏幕上的確切座標。下一個階段是繪製- 呈現引擎會遍歷呈現樹,由用戶界面後端層將每一個節點繪製出來。

2、解析

在呈現引擎中解析是很是重要的環節。解析文檔便是將文檔轉化爲有意義的結構,解析後獲得的結果一般表明了文檔結構的節點樹,被稱做解析樹或語法樹。

而解析的過程通常爲詞法分析和語法分析,詞法分析便是大量的標記過程,詞法分析器根據特定的字典(語言的詞彙)對輸入內容進行標記;語法分析便是應用語言語法的過程。不一樣語言擁有不一樣的解析器,在這裏不作多的贅述,若是想了解更多,那就去了解吧。

在瀏覽器中,有HTML解析器,CSS解析器,JavaScript解析器等。

HTML解析

HTML解析器輸出的解析樹是由DOM元素和屬性節點構成的樹結構(內容樹或DOM樹)。

具體解析算法:標記化和樹構建,分別有標記生成器和樹構建器完成

做者注:在這裏提到的」標記」即爲咱們常說的標籤

標記化算法:輸入HTML內容,輸出HTML標記

在查看具體算法以前,咱們須要先了解一些基礎的東西,便於對算法的理解。一個HTML元素包括其起始標記,結束標記,屬性名稱和屬性值,以及內容。標記化經過狀態機來表示,每個狀態接收輸入的一個或多個字符,並根據這些字符更新狀態。
具體算法以下:

       初始狀態:數據狀態

       遇到」 < 」:標記打開狀態

       遇到字符:標記名稱狀態(上一狀態爲標記打開狀態) // 這裏原文說的是a-z

       遇到「/ 「:建立end tag token(上一狀態爲標記打開狀態)

       遇到字符:發送字符標記(上一狀態爲數據狀態)

       遇到」 > 」:發送當前標記,修改狀態爲數據狀態(上一狀態爲標記打開狀態)

樹構建算法

在解析器標記化時,也會進行樹構建。DOM樹以Document爲根節點,經過樹構建器接收標記生成器發送的標記,並根據規範匹配並建立對應的DOM元素,建立的元素將被添加到DOM樹以及開放的堆棧中。

原文注:此堆棧用於糾正嵌套錯誤和處理未關閉的標記

樹構建的流程好像搞得半懂不懂的,有機會研究下源碼,再記錄吧。這裏先大概記錄下如今的理解吧。首先樹構建器接收到標記,會根據狀態判斷以及容錯規則判斷處理方式,重複該操做。

  1. 狀態處理規則:InitialMode,BeforeHTMLMode, BeforeHeadMode, InHeadMode, AfterHeadMode, InBodyMode,InTableMode, InCapationMode, InColumnGroupMode等,在這些狀態時會根據接收到的標記進行判斷,不符合規範則系統隱式建立符合規範的元素,附加到DOM樹,並從新處理接收到的標記。

  2. 容錯處理規則:

如下片斷原文使用的引用,引用自W3C規範
1.明顯不能在某些外部標記中添加的元素。在此狀況下,咱們應該關閉全部標記,直到出現禁止添加的元素,而後再加入該元素。

2.咱們不能直接添加的元素。這極可能是網頁做者忘記添加了其中的一些標記(或者其中的標記是可選的)。這些標籤可能包括:HTML HEAD BODY TBODY TR TD LI(還有遺漏的嗎?)。
3.向inline 元素內添加 block 元素。關閉全部inline 元素,直到出現下一個較高級的 block 元素。若是這樣仍然無效,可關閉全部元素,直到能夠添加元素爲止,或者忽略該標記。

CSS解析

CSS解析和HTML不一樣,HTML因爲要求處理較爲「寬容「,容許省略某些隱式添加的標記,有時還能省略一些起始或者結束標記等等,他的語法不是與上下文無關的語法,以致於HTML 並不能很容易地用解析器所需的與上下文無關的語法來定義。而CSSCSS 是上下文無關的語法,可使用常規解析器進行解析,瀏覽器的CSS解析器根據規範生成,如WebKit使用Flex和Bison解析器生成器,經過
CSS 語法文件自動建立解析器。向解析器輸入css樣式,解析器則輸出相應的解析樹。

3、呈現樹構建

呈現樹構建時間在文章中呈現引擎工做流程已介紹,它的做用就是按照正確的順序繪製內容。Firefox 將呈現樹中的元素稱爲「框架」。WebKit 使用的術語是呈現器或呈現對象。在原文中做者使用「呈現器」進行接下來的介紹,爲了方便理解咱們強制記住如下使用的「呈現器」即表示呈現樹種的元素。

呈現樹的構建基於DOM元素和CSS規則,而呈現樹則是佈局和繪製的基礎,呈現樹經過構建與DOM節點的呈現器以及計算相關節點的CSS規則構建。

呈現器構建規則:

呈現構造器根據css中定義的「display」屬性以及元素類型建立不一樣的呈現器類型,每一個呈現器都表明一個矩形框,可一般對應於相關節點的css框,包含了寬度,高度,位置等幾何信息。這裏根據不一樣元素主要是一些特殊元素,如表單,表格等,這些呈現器包含一些除了幾何信息外的信息。

做者注:原本還覺得呈現器的矩形框便是咱們常說的盒子呢,後來才知道CSS框模型是佈局後的矩形框。因此我好像猜對了。

樣式計算:

在構建呈現樹時,須要經過計算每一個元素的樣式屬性來計算每一個呈現器的可視化屬性。其中樣式來源包括瀏覽器的默認樣式表、由網頁提供的樣式表以及由用戶提供給瀏覽器的用戶樣式表。

固然計算樣式有不少難點,好比數據複雜的結構,這裏就不列出來了,有需求的能夠閱讀原文,這裏只簡單介紹瀏覽器如何計算。

樣式的計算會用CSS解析器的解析結果,系統根據以樣式規則最右邊的選擇器爲鍵(這也解釋了爲何CSS是從右向左匹配)將CSS規則(總體)添加到不一樣的選擇器哈希表,如ID表,類表等。計算是隻要從哈希表中提取元素相關的規則便可(這能夠不考慮與元素無關的規則,從而減小計算)。剩下的工做就是從提取的規則中判斷真正匹配元素的規則。

做者注:這裏所說的CSS規則就是咱們開發者寫的CSS規則,以下便是一條規則

#my-container {
    color: #fff;
    background: #fff;
}

樣式表層疊順序:

樣式層疊順序規則:瀏覽器申明<用戶普通聲明<做者普通聲明<做者重要聲明<用戶重要聲明。做者即開發者,用戶即瀏覽器使用者。

特異性規則:按a-b-c-d順序。a表示是否爲style屬性,是則爲1,不然爲0;b表示選擇器ID的個數;c表示選擇器中類,僞類,以及其餘元素的個數;d表示元素名稱和僞元素的個數。如form#form1 input[type=number] + label.input-label:before {…} 可表示爲a=0 b=1 c=2 d=4

這也就導出了爲何屬性可經過標記1000這樣的權重計算規則。固然瀏覽器內部具體如何實現計算,仍是不知道,對於普通開發者也不必知道,這也足夠解決開發問題了。

做者注: 能夠看關於別人前端面試的問題的回答(CSS篇)中的關於CSS權重計算的問題

DOM樹與呈現樹的聯繫與區別

呈現器與DOM元素是相對應,但並不是一一對應的。非可視化的元素不會插入呈現樹,如HRAD,display屬性爲「none」的元素。而像「select」這樣的複雜結構元素,則須要多個呈現器。

呈現樹構建流程:

在Firefox中,呈現引擎(原文用的」系統」)經過註冊展現層做爲偵聽器。經過展現層委託給FrameConstructor來解析樣式並建立呈現器。在WebKit中,把解析樣式和建立呈現器的過程稱爲」附加」(原文使用了「附加(Attachment)」,我的以爲該詞不翻譯更好)。附加是同步進行的,經過調用新節點的「attach」方法把節點插入DOM樹,而調用節點的」attach」方法會根據建立呈現器規則判斷是否須要建立呈現器,須要則建立呈現器。

圖片來自其餘博客

做者注: 圖片原文地址

4、佈局

呈現器在建立完成並添加都呈現樹時並不包含位置和大小信息,把計算這些值得過程成爲佈局或重排。HTML採用基於流的佈局模型,意味着大多數狀況下只要一次遍歷就能計算出幾何信息(位置,大小),處於流中靠後位置的元素一般不會影響靠前位置元素的幾何特徵。

佈局是一個遞歸的過程,從根呈現器(HTML元素)開始(0,0位置,瀏覽器左邊),遞歸遍歷部分或所有呈現器,並經過調用須要佈局的子呈現器的」layout」或「reflow」方法計算子呈現器的幾何信息(父元素被子元素撐開)。

而爲了不對全部細微的改動都進行總體佈局(重繪),瀏覽器採用了一種」dirty」系統,對發生改變的呈現器,瀏覽器將其標記爲「dirty」,表示須要從新佈局。有兩種標記「dirty」和「children are dirty」。「children are dirty」表示儘管呈現器自身沒有改變,可是它至少一個子代須要從新佈局(那麼這種狀況究竟是隻佈局子代仍是會佈局這個呈現器自己和其子代呢?)。

當全部呈現器的全局樣式(如字體大小更改)或者屏幕大小發生改變時會觸發全局佈局,當部分呈現器發送改變(標記爲「dirty」)時會觸發增量佈局,如腳本操做DOM。

增量佈局是異步執行的,Firefox使用隊列,由調度程序觸發佈局,而WebKit使用定時器對呈現樹進行遍歷,對標記爲「dirty」的呈現器進行佈局。全局佈局每每是同步的。

佈局過程:
  1. 父呈現器肯定本身高度

  2. 父呈現器處理子呈現器,爲其設置座標,在子呈現器爲「dirty」或全局佈局時,爲子呈現器計算高度

  3. 父呈現器根據子呈現器高度以及邊框,邊距,補白等信息累加設置自身高度(遞歸的過程)。

  4. 將「dirty」標記改成false

若是在佈局過程當中,呈現器須要換行,則會中止佈局,並告知父呈現器須要換行,父呈現器則會建立額外的呈現器,並進行佈局。

5、繪製

系統遍歷呈現樹,並調用呈現器的「paint」方法,將呈現器的內容顯示在屏幕上的過程稱爲繪製。

跟佈局同樣,繪製也分爲全局繪製和增量繪製。在增量繪製中,部分呈現器發生了更改,可是不會影響整個樹。更改後的呈現器將其在屏幕上對應的矩形區域設爲無效,這致使OS 將其視爲一塊「dirty 區域」,並生成「paint」事件。

原文在這裏對Firefox和chrome進行了介紹

在介紹繪製順序前,先介紹下CSS的分層展現。分層有CSS的z-index屬性指定,它表明了框的第三維度,也就是沿「z軸」方向的位置。這些框分散在多個堆棧(稱爲堆棧上下文),在每一個堆棧中,會首先繪製後面的元素,而後再頂部繪製前面的元素,以便更靠近用戶,對於重疊的元素,則最後繪製的在最前面。具備z-index屬性的框造成了本地堆棧,視口具備外部堆棧(因此若是「dirty」會觸發增量繪製?)。

接着介紹繪製順序,繪製的順序就是元素進入堆棧的順序,這些堆棧會從後向前繪製,所以這樣的順序會影響繪製。而塊呈現器的堆棧順序爲:背景色,背景圖片,邊框,子代,輪廓。

6、其餘

呈現引擎線程

呈現引擎採用單線程,除網絡操做外的全部操做都在單線程中進行。除chrome瀏覽器該線程爲標籤頁線程,其餘瀏覽器都使用瀏覽器主線程。網絡操做可由多個並行線程執行。

事件循環

瀏覽器的主線程是事件循環,是一個無限循環,永遠等待時間發生,並進行處理。

7、正文結束

雖然原文總的來講看了兩遍,可是以爲仍是不少地方似懂非懂,但願有機會查看一次他的源代碼吧,不只僅是弄清楚瀏覽器如何運行,更是但願可以重中學到其餘工程師如何處理結構如此複雜的數據。

最後附上原文的兩張主流程圖

WebKit

Firefox

寫在最後

成本價:約¥500

價值幾何:看官來講

歡迎關注微信號: 成長之路。提出你的問題,交給我來回答。一塊兒踏上成長之路,在文章評論中留言你的問題也有機會被選中哦。

若是你發現文中有錯誤的地方,請指出,我將及時修改。

整理的很差,很是有幸被你看到,請指點。

相關文章
相關標籤/搜索