對於這個過程,我是真的看了好多資料都沒能達到很是透徹的理解,如下是我所能瞭解並理解的一些:特此整理並記錄下來,僅供本身的學習啦javascript
關於瀏覽器的一些基礎知識css
瀏覽器的主要功能是向服務器發出請求,在瀏覽器窗口中顯示咱們所選擇的的網絡資源,咱們所選擇的網絡資源通常是指html文檔,PDF,圖片或其餘類型的東西(動態文件之類的),資源的位置由用戶使用的URL(統一資源定位符)指定。html
瀏覽器解釋並解釋html文件的方式是在html和css規範中指定的,這些規範由W3C進行維護,可是這麼多年過去了,各大瀏覽器並無徹底聽從這些規範,同時還在開發本身獨有的擴展程序,這對前端開發人員來講就真的太苦逼了,這種兼容性問題實在頭疼,現在,大多數瀏覽器是或多或少的聽從W3C的規範。前端
用過各類瀏覽器的確定也注意到它們彼此之間是有不少相同的元素的,其中包括:html5
輸入欄(URL)java
前進和後退node
書籤設置linux
刷新按鈕和中止加載按鈕web
返回主界面的按鈕正則表達式
各瀏覽器也都還有着本身各自的功能,好比Firefox的下載管理器之類的。
瀏覽器的高層結構
1.用戶界面-包括地址欄,前進、後退按鈕書籤單等,除了瀏覽器主窗口的顯示的頁面外,其餘顯示的各個部分都屬於用戶界面。
2瀏覽器引擎-在用戶界面和呈現引擎之間傳送指令。
3呈現引擎-負責顯示請求的內容。若是請求的內容是html,它就解析html和css內容,並將解析後的內容顯示在屏幕上。
4.網絡-用於網絡調用,好比http請求。這個接口與平臺無關,併爲全部平臺提供底層實現。
5.用戶界面後端-用於繪製基本的窗口小部件,好比組合框和窗口,其公開了與平臺無關的通用接口,而在底層使用操做系統的用戶界面方法
6.Javascript解析器,用於解析和執行javascript代碼。
7.數據存儲,也就是咱們的瀏覽器的存儲,像cookie啦,localstroage啦,sessionstorage(講到這我又想回顧一下這三個東西的區別了,先說一下,這三個都是存儲在瀏覽器端的,主要是存儲的大小不一樣,數據有效期不一樣,仍是改天再另外擼一篇吧!)h5定義了‘網絡數據庫’,這是一個完整可是輕便的瀏覽器內存數據庫。
值得注意的是,和大多數瀏覽器不一樣,Chrome瀏覽器的每一個標籤頁都對應一個呈現引擎實例。每一個標籤頁都是一個獨立的進程(那麼這個就是它完虐各大瀏覽器的緣由之一???,我猜的)
呈現引擎
呈現引擎的做用很明顯就是呈現咯,也就是在瀏覽器的屏幕上顯示請求的內容。
默認狀況下,呈現引擎可顯示html和XML文檔與圖片。經過插件(瀏覽器的擴展程序),還能夠顯示其餘類型的內容;使用PDF查看器插件就能顯示PDF文檔。可是他的主要用途是:顯示使用css格式化的html內容和圖片。
那麼呈現引擎也有好幾種,Firefox使用的是Gecko,這是mozilla公司本身開發的呈現引擎,Chrome和safair都是webkit。
webkit是一種開放源代碼的呈現引擎,起初用於linux平臺,隨後由apple公司修改,從而它是支持蘋果機和windows的。
接下來看主流程
接下來重點來啦:
呈現引擎將開始解析html文檔,並將各標記逐個轉化成內容樹上的DOM節點。同時會解析外部css文件以及樣式元素中的樣式數據。html中這些帶有視覺指令的樣式信息將用於建立另外一個樹結構,我叫它渲染樹,也有人叫它呈現樹。
渲染樹包含多個視覺屬性的矩形,這些矩形的排列順序就是他們將在屏幕上顯示的順序。
渲染樹構建完畢後,進入佈局處理階段,也就是爲每一個節點分配一個應出如今屏幕上的確切座標,下一個階段是繪製--這時呈現引擎會遍歷渲染樹,由用戶界面後端將每一個節點繪製出來。
須要着重指出的是,這是一個漸進的過程,爲了達到更好的用戶體驗,呈現引擎會力求儘快將內容顯示在屏幕上,它沒必要等到整個html文檔解析完畢,就會開始構建渲染樹和設置佈局,在不斷的接收和處理來自網絡的其他內容的同時,呈現引擎將會部份內容解析並呈現出來。
雖然二者的術語不一樣,但總體流程是基本相同的。
Gecko將視覺格式化元素組成的樹稱爲框架樹。每一個元素都是一個框架。webkit則是呈現樹,它由呈現對象組成,對於元素的位置,webkit使用的術語是佈局,而Gecko稱之爲重排。對於鏈接DOM節點和可視化信息從而建立呈現樹的過程,webkit稱爲附加,有一個細微的非語義化差異,就是Gecko和html與DOM樹之間還有一個稱爲內容槽的層,用於生成DOM元素。
接下來逐一論述流程中的每個部分:
解析-綜述
解析是呈現引擎中很是重要的一個環節,所以咱們要更深刻地理解。首先,先來介紹一下解析。
解析文檔是指將文檔轉化成爲有意義的結構,也就是可以讓代碼理解和使用的結構。解析獲得的結果一般是表明了文檔結構的節點數,它稱爲解析樹或者語法樹。
講了這麼久的樹,那麼什麼是樹,來看:
語法:解析是以文檔所聽從的語法規則(編寫文檔所用的語言或格式)爲基礎的,全部能夠解析的格式都必須對應肯定的語法(由詞彙和語法規則構成)。這稱爲與上下文無關的語法。人類語言並不屬於這樣的語言,所以沒法用常規的解析技術進行解析。
(以上這段,我目前都還不是很懂,之後回來看看吧)
解析器和詞法分析器的組合
解析的過程能夠分爲兩個子過程:詞法分析和語法分析。
詞法分析是將輸入內容分割成大量標記的過程。標記是語言中的詞彙,即構成內容的單位,在人類語言中,它至關於語言字典中的單詞。
語法分析是應用語言的語法規則的過程
解析器一般將解析工做分給兩個組件來處理:詞法分析器(有時候也叫標記生成器),負責將輸入內容分解成一個個有效的標記;而解析器負責根據語言的語法規則來分析文檔結構,從而構建解析樹。詞法分析器知道如何將無關的字符(好比空格和換行符)分離出來。
解析是一個迭代的過程。一般,解析器會向詞法分析器請求一個新標記,並嘗試將其與某條語法規則進行匹配。若是發現了匹配規則,則解析器會將一個對應於標記的節點添加到解析樹中,而後繼續請求下一個標記。
若是沒有規則可匹配,解析器就會將標記存儲到內部,並繼續請求標記,直至找到可與全部內部存儲的標記匹配的規則。若是找不到任何匹配規則,解析器就會引起一個異常。這意味着文檔無效,包含語法錯誤。
翻譯:
不少時候,解析樹不是最終產品。解析一般是在翻譯過程當中使用的,而翻譯是指將輸入文檔轉換成另外一種格式。編譯就是這樣一個例子。編譯器可將源代碼編譯成機器代碼,具體過程是首先將源代碼解析成解析樹,而後將解析樹翻譯成機器代碼文檔。
解析示例:
以前已經看了經過數學表達式簡歷瞭解析樹。如今,讓咱們試着定義一個簡單的數學語言,用來演示解析的過程。
1:構成語言的語法單位是表達式,項和運算符。
2.咱們用的語言能夠包含任意數量的表達式。
3.表達式的定義是:一個項接一個運算符,而後再接一個項。
4.運算符是加號或減號。
5.項是一個整數或一個表達式。
好了,能夠分析 2+3—1了
匹配語法規則的第一個子串是2,而根據第五條詞法規則,這是一個項。匹配語法規則的第二個子串是2+3,而根據第三條規則,這是一個表達式。下一個匹配項已經到了輸入的結束。2+3-1是一個表達式,由於咱們已經知道2+3是一個項,這樣就符合第三條條規則。2++不與任何規則匹配,所以是無效的輸入。
好像有點兒懂了
解析器類型
有兩種基本類型的解析器:自上而下解析器和自下而上解析器。直觀來講,自上而下的解析器從語法的高層結構出發,嘗試從中找到匹配的結構。而自下而上的解析器從底層規則出發,將輸入內容轉化爲語法規則,直至知足高層規則。
接下來能夠看看這兩種解析器會如何解析咱們的示例:
自上而下的解析器會從高層開始:首先將2+3標識爲一個表達式,而後將2+3-1標識爲一個表達式(標識表達式的過程涉及到匹配其餘規則,可是起點是最高級別的規則,懂)
自下而上的解析器將掃描輸入內容,找到匹配的規則後,將匹配的輸入內容替換成規則。如此繼續替換,直至輸入內容的結尾。部分匹配的表達式保存在解析器的堆棧中。
這種自下而上的解析器稱爲移位歸約解析器,由於輸入在向右移位(設想有一個指針從輸入內容的開頭移動到結尾),而且逐漸歸約到語法規則上。
不懂
自動生成解析器
有一些工具是能夠幫助生成解析器的,它們稱爲解析器生成器。咱們只要提供咱們所用的語法(語法規則和詞彙),它就會生成相應的解析器。建立解析器須要對解析器有深入的理解,而人工建立優化的解析器並非一件容易的事情,因此解析器生成器是很是實用的。
webkit實用了兩種很是有名的解析器生成器:用於建立詞法分析器的flex以及用於建立解析器的bison。flex的輸入是包含標記的正則表達式定義的文件,bison的輸入是採用BNF格式的語言語法規則。
HTML解析器
html解析器的任務是將html標記解析成解析樹。
html語法定義
html的詞彙和語法在W3C組織建立的規範中進行了定義。當前的版本是html5.
非與上下文無關的語法
正如咱們在解析過程的簡介中已經瞭解到,語法能夠用BNF等格式進行正式定義。
很遺憾,全部的常規解析器都不適用於HTML(啊,我才知道)。html並不能很容易地用解析器所需的與上下文無關的語法來定義。有一種能夠定義html的正規格式:DTD(document type definition),但它不是與上下文無關額語法。
這起初看起來很奇怪:html與XML很是類似。有不少XML解析器可使用。html存在一個XML變體(XHTML),那麼有什麼大的區別呢,區別在於html的處理更爲「寬容」,它容許你省略某些隱式添加的標記,有時候還能省略一些起始或結束的標記等,和XML嚴格的語法不一樣,html總體來看是一種「軟性」的語法。
顯然,這種看上去細微的差異實際上卻帶來了巨大的影響。一方面,這是HTML如此流行的緣由:它能包容咱們的錯誤;簡化網絡開發。另外一方面,這使得它很難編寫正式的語法。歸納的說,html沒法很容易地經過常規解析器解析(由於它的語法不是與上下文無關的語法),所以也沒法經過XML解析器來解析。
HTML DTD
html的定義採用了DTD格式,這個格式可用於定義SGML族的語言。(什麼鬼)。它包含了全部容許使用的元素及其屬性和層次結構的定義。如上文所述,HTML DTD沒法構成與上下文無關的語法。
DTD存在一些變體。嚴格模式徹底遵循html規範,而其餘模式可支持之前的瀏覽器多使用的標記。這樣作的目的是確保向下兼容一些舊版本的內容。最新的嚴格模式DTD在這http://www.w3.org/TR/html4/strict.dtd
DOM
解析器的輸出「解析樹」是由DOM元素和屬性節點構成的樹結構。DOM是文檔對象模型的縮寫,它事HTML文檔的對象表示,同時也是外部內容(css js)與html元素之間的接口。
解析算法
HTML沒法用常規的自上而下或自下而上的解析器進行解析。緣由在於:
1.語言的寬容本質。
2.瀏覽器從來對一些常見的無效html用法採起包容的態度。
3.解析過程須要不斷地反覆。源內容在解析過程當中一般不會改變,可是在HTML中,腳部標記若是包含document.write,就會添加額外的標記,這樣解析過程實際上就更改了輸入內容。
因爲不能使用常規的解析過程,瀏覽器就建立了自定義的解析器來解析HTML。http://www.whatwg.org/specs/web-apps/current-work/multipage/parsing.html,這個地址是html5規範詳細德描述瞭解析算法。此算法由兩個階段組成:標記化和樹構建。
標記化是詞法分析過程,將輸入內容解析成多個標記。HTML標記包括起始標記,結束標記,屬性名和屬性值。標記生成器識別標記,傳遞給樹構造器,而後接受下一個字符以識別下一個標記;如此反覆知道輸入結束。
標記化算法
該算法的輸出結果是html標記。該算法使用狀態機來表示。每個狀態接收來自輸入信息流的一個或多個字符,並根據這些字符更新下一個狀態。當前標記化狀態和樹結構狀態會影響下一個狀態的決定。這意味着,即使接收的字符相同,對於下一個正確的狀態也會產生不一樣的結果,具體取決於當前的狀態。該算法至關複雜,沒法詳述,因此咱們經過一個簡單的示例來理解。
將下面的html代碼標記化:
<html>
<body>
hello xiaoai
</body>
</html>
初始化時數據狀態。遇到字符<時,狀態更改成「標記打開狀態」。接收一個a-z字符會建立「起始標記」,狀態更改成「標記名稱狀態」。這個狀態會一直保持到接收>。在此期間接收的每一個字符都會附加到新的標記名稱上。
遇到>標記時,會發送當前的標記,狀態改回「數據狀態」。<body>標記也會進行一樣的處理。目前html和body標記均已發出。如今咱們回到「數據狀態」。接收到hello xiaoai 中的h字符時,將建立併發送字符標記,直到接收</body>中的<。咱們將爲hello xiaoai 中的每個字符都發送一個字符標記。(不錯,講得好詳細,<攤手>)
如今回到「標記打開狀態」。接收下一個輸入字符/時,會建立 end tag token 並改成「標記名稱狀態」。咱們會再次保持這個狀態,直到回收>.而後將發送新的標記,並回到「數據狀態」。</html>輸入也會一樣的處理。
樹構建算法
在建立解析器的同時,也會建立document對象。在樹構建階段,以document爲根節點的DOM樹也會不斷進行修改,向其中添加各類元素,標記生成器發送的每一個節點都會由樹構建器進行處理。規範中定義了每一個標記所對應的dom元素。這些元素會在接收到相應的標記時建立。這些元素不只會添加到dom樹中,還會添加到開放元素的堆棧中。此堆棧用於糾正嵌套錯誤和處理未關閉的標記。其算法也能夠用狀態機來描述。這些狀態稱爲「插入模式」。
仍是上面的代碼,來看看樹構建過程。
<html>
<body>
hello xiaoai
</body>
</html>
樹構建階段的輸入是一個來自標記化階段的標記序列。第一個模式是「initial mode」。接收html標記後轉爲「before html」模式,並在這個模式下從新處理此標記。這樣會建立一個htmlelement元素,並將它附加到document的根對象上。
而後狀態改成「before head」。此時咱們接收「body」標記。即便咱們的示例中沒有「head」標記,系統也會隱式建立一個html headelement,並將它附加的樹中。
如今進入了「 in head」模式,而後轉入「after head」模式。系統對body 標記進行從新處理,建立並插入HTML bodyelement ,同時模式轉變爲「body」
如今,接收由「hello xiaoai」字符串生成的一系列字符標記。接收第一個字符時會建立並插入「Text」節點,而其餘字符也將附加到該節點。
接收body結束標記會觸發「after body」模式。如今咱們將接收html結束標記,而後進入「after after body」模式。接收到文件結束標記後,解析就此結束。
解析結束後
在此階段,瀏覽器會將文檔標註爲交互狀態,並開始解析那些處於「deferred」模式的腳本,也就是那些應在文檔解析完成後才執行的腳本。而後,文檔狀態設置爲「完成」,一個「加載」事件將隨之觸發,連接:http://www.w3.org/TR/html5/syntax.html#html-parser
瀏覽器的容錯機制
咱們在瀏覽html網頁時歷來不會看到「語法無效」的錯誤,這是由於瀏覽器會糾正任何無效內容,而後繼續工做。
HTML5 規範了一部分這樣的要求,webkit在html解析器類的開頭註釋對此做了很好的歸納:
瀏覽器對標記化輸入內容進行解析,以構建文檔樹。若是文檔的格式正確,就直接進行解析。
遺憾的是,咱們不得不處理不少格式錯誤的html文檔,因此解析器必須具有必定的容錯性。
咱們至少可以處理如下的錯誤狀況:
1明顯不能在某些外部標記中添加的元素。此狀況下,咱們應該關閉全部的標記,直到出現禁止添加的元素,而後再加入該元素。
2.咱們不能直接添加的元素。這極可能是網頁做者忘記添加了其中的一些標記(或者其中的標記是可選的)。這些標籤可能包括:HTML head body tbody tr td li。
3.向inline元素內添加block 元素。關閉全部inline元素,直到出現下一個較高級的block元素。
4.若是這樣仍然無效,可關閉全部元素,直到能夠添加元素爲止,或者忽略該標記。
以上這麼多都在講html的解析,接下來看看css的解析
css 解析
和html不一樣,css是上下文無關的語法,可使用各類解析器進行解析。事實上,css規範定義了css的詞法和語法。連接:http://www.w3.org/TR/CSS2/grammar.html
webkit css解析器
webkit使用flex和bison解析器生成器,經過css語法文件自動建立解析器。正如咱們以前在解析器的介紹中所說,bison會建立自下而上的移位規約解析器。Firefox使用的是人工編寫的自上而下的解析器。這兩種解析器會將css文件解析成stylesheet對象,且每一個對象都包含css規則。css規則包含選擇器和聲明對象,以及其餘與css語法對應的對象。
不太懂
接下來腳本!
處理腳本和樣式表的順序
腳本
網絡的模型是同步的,網頁做者但願解析器遇到<script>標記時當即解析並執行腳本,文檔的解析將中止,直到腳本執行完畢。若是腳本是外部的,那麼解析過程會中止,直到從網絡同步抓取資源完成後再繼續。此模型已經用了不少年了,也在HTML4和html5規範中進行了指定。做者也能夠將腳本標註爲「defer」,這樣它就不會中止解析文檔,而是等到解析結束後才執行。h5增長了一個選項,可將腳本標記爲異步,以便其餘線程解析和執行。
預解析
webkit和Firefox都進行了這項優化。在執行腳本時,其餘線程會解析文檔的其他部分,找出並加載須要經過網絡加載的其餘資源。經過這種方式,資源能夠在並行鏈接上加載,從而提升整體速度,可是,預解析器不會修改DOM樹,而是將這項工做交由主解析器處理;預解析器只會解析外部資源(js,css,圖片)的引用。
樣式表
另外一方面,樣式表有着不一樣的模型。理論上來講,應用樣式表不會更改dom樹,所以彷佛沒有必要等待樣式表並中止解析。但這涉及到一個問題,就是腳本在文檔解析階段會請求樣式信息,若是當時尚未加載和解析樣式,腳本就會得到錯誤的回覆,這樣顯然會產生不少問題。這看上去是一個非典型案例,可是事實上很是廣泛。Firefox在樣式表加載和解析的過程當中,會禁止全部的腳本。而對於webkit而言,僅當腳本嘗試訪問樣式屬性可能受還沒有加載的樣式表影響時,它纔會禁止該腳本。
呈現樹的構建
在DOM樹構建的同時,瀏覽器還會構建另外一個樹結構:呈現樹。這是可視化元素按照其顯示順序而組成的樹,也是文檔的可視化表示。它的做用是按照正確順序繪製內容。
Firefox 將呈現樹中的元素稱爲「框架」。webkit使用的術語是呈現器或呈現對象。
呈現器知道如何佈局並將自身及其子元素繪製出來。
webkit的呈現對象類是全部呈現器的基類。
class
RenderObject{
virtual void layout();
virtual void paint(PaintInfo);
virtual void rect repaintRect();
Node* node;
//the DOM node
RenderStyle* style;
// the computed style
RenderLayer* containgLayer;
//the containing z-index layer
}