做爲一名網絡開發人員,學習瀏覽器的內部工做原理將有助於您做出更明智的決策,並理解那些最佳開發實踐的箇中原因。儘管這是一篇至關長的文檔,可是咱們建議您花些時間來仔細閱讀;讀完以後,您確定會以爲所費不虛。 css
保羅·愛麗詩(Paul Irish),Chrome 瀏覽器開發人員事務部html
瀏覽器是使用最廣的軟件之一。這篇博文中,我將簡單介紹瀏覽器的工做原理。經過閱讀本文,咱們將會了解,從您在地址欄輸入 google.com ,直到您在瀏覽器屏幕上看到 Google 首頁的整個過程當中都發生了些什麼。web
瀏覽器的主要功能就是向服務器發出請求,在瀏覽器窗口中展現您想要訪問的網絡資源。資源的位置由用戶使用 URI(統一資源標示符)指定。正則表達式
瀏覽器的主要組件包括:算法
圖1.1:瀏覽器組件編程
渲染引擎是瀏覽器的核心,也能夠叫作瀏覽器的內核。他的功能是渲染,即在瀏覽器窗口中顯示所請求的內容。默認狀況下,渲染引擎可顯示 HTML 和 XML 文檔及圖片。後端
Firefox 使用的是 Gecko,這是 Mozilla 公司「自制」的渲染引擎。而 Safari 和 Chrome(28版本之前)瀏覽器使用的都是 Webkit。2013年7月10日發佈的Chrome 28 版本中,Chrome瀏覽器開始正式使用Blink內核。因此,Webkit已經成爲了Chrome瀏覽器的前內核。瀏覽器
渲染引擎一開始會從網絡層獲取請求文檔的內容(以8k分塊)。緩存
解析HTML文檔,並將文檔中的標籤轉化爲dom節點樹,即」內容樹」。同時,它也會解析外部CSS文件以及style標籤中的樣式數據。這些樣式信息連同HTML中的」可見內容」一道,被用於構建另外一棵樹——」渲染樹(Render樹)」。服務器
渲染樹由一些帶有視覺屬性(如顏色、大小等)的矩形組成,這些矩形將按照正確的順序顯示在頻幕上。
渲染樹構建完畢以後,將會進入」佈局」處理階段,即爲每個節點分配一個屏幕座標。再下一步就是繪製(painting),即遍歷render樹,並使用UI後端層繪製每一個節點。
這個過程是逐步完成的,爲了更好的用戶體驗,渲染引擎將會盡量早的將內容呈現到屏幕上,並不會等到全部的html都解析完成以後再去構建和佈局render樹。它是解析完一部份內容就顯示一部份內容,同時,可能還在經過網絡下載其他內容。
圖2.1 Webkit 主流程
圖2.2 Mozilla 的 Gecko 渲染引擎主流程
兩種渲染引擎的區別:
解析一個文檔就是指將這個文檔翻譯成一個可讓代碼理解和使用的有意義的結構。獲得的結構一般是一個表明了該文檔結構的節點樹,一般稱之爲解析樹或語法樹。
解析是以文檔所遵循的語法規則(編寫文檔所用的語言或格式)爲基礎的。全部能夠解析的格式都必須對應肯定的語法(由詞彙和語法規則構成)。這稱爲與上下文無關的文法。
注意人類語言,HTML都不屬於這樣的語言,所以沒法用常規的解析技術進行解析。
1)解析通常可分爲兩個子過程
語法分析:對語言應用語法規則。
詞法分析:將輸入分解爲符號,符號是語言的詞彙表——基本有效單元的集合。對於人類語言來講,它至關於咱們字典中出現的全部單詞。
2)解析工做通常由兩個組件共同完成:
詞法分析器(有時也稱爲標記生成器):負責將輸入內容分解成一個個有效標記。詞法分析器知道如何將無關的字符(好比空格和換行符)分離出來。
解析器:根據語言的語法規則分析文檔的結構,從而構建解析樹。
3)解析過程:
解析是一個迭代的過程。一般,解析器會向詞法分析器請求一個新標記,並嘗試將其與某條語法規則進行匹配。若是發現了匹配規則,解析器會將一個對應於該標記的節點添加到解析樹中,而後繼續請求下一個標記。
若是沒有規則與該標記匹配,解析器就會將標記存儲到內部,並繼續請求下一個標記,直至找到可與全部內部存儲的標記匹配的規則。
若是沒有規則(即沒有找到相應的語法規則),解析器就會引起一個異常。這意味着文檔無效,包含語法錯誤。
不少時候,解析樹還不是最終結果。解析一般是在轉換過程當中使用的,而轉換是指將輸入文檔轉換成另外一種格式。
編譯就是一個例子。編譯器可將源代碼編譯成機器代碼,具體過程是首先將源代碼解析成解析樹,而後將解析樹翻譯成機器代碼文檔。
1)詞彙一般用正則表達式表示。
2)語法一般使用一種稱爲 BNF 的格式來定義。
與上下文無關的語法的直觀定義就是能夠徹底用BNF格式表達的語法。
有兩種基本類型的解析器:自上而下解析器和自下而上解析器。直觀地來講,自上而下的解析器從語法的高層結構出發,嘗試從中找到匹配的結構。而自下而上的解析器從低層規則出發,將輸入內容逐步轉化爲語法規則,直至知足高層規則。
解析器生成器這個工具能夠自動生成解析器,只須要指定語言的文法———詞彙表及語法規則,它就能夠生成一個解析器。建立一個解析器須要對解析有深刻的理解,並且手動的建立一個有較好性能的解析器並不容易,因此解析生成器頗有用。
Webkit使用兩個知名的解析生成器——用於建立語法分析器的Flex及建立解析器的Bison(你可能接觸過Lex和Yacc)。Flex的輸入是一個包含了符號定義的正則表達式,Bison的輸入是用BNF格式表示的語法規則。
HTML 解析器的任務是將 HTML 標記解析成解析樹。
正如在解析簡介中提到的,上下文無關文法的語法能夠用相似BNF的格式來定義。
很遺憾,全部的常規解析器都不適用於 HTML(我並非開玩笑,它們能夠用於解析 CSS 和 JavaScript)。HTML 並不能用解析器所需的與上下文無關的語法來定義。
Html有一個正式的格式定義:DTD(Document Type Definition,文檔類型定義),但它並非上下文無關的語法。
HTML的定義採用了DTD格式。此格式適用於定義SGML族的語言。它包括全部容許使用的元素及其屬性和層次結構的定義。如上文所述,HTML DTD沒法構成與上下文無關的語法。
解析器的輸出(即」解析樹」)是由DOM元素及屬性節點組成的。DOM是文檔對象模型(Document Object Model) 的縮寫。它是HTML文檔的對象表示,同時也是外部內容(例如 JavaScript)與HTML元素之間的接口。
解析樹的根節點是」Document」對象。DOM與標記之間幾乎是一一對應的關係。
這裏所說的DOM節點樹,指的是那些實現了DOM接口的元素組成的樹。
HTML沒法用常規的自上而下或自下而上的解析器進行解析。緣由在於:
語言自己的寬容特性;
瀏覽器對一些常見的非法html有容錯機制;
解析過程須要不斷地反覆。源內容在解析過程當中一般不會改變,可是在HTML中,腳本標記若是包含 「document.write」,就會添加額外的標記,這樣解析過程實際上就更改了輸入內容。
因爲不能使用常規的解析技術,瀏覽器爲html定製了專屬的解析器。
HTML5規範詳細地描述瞭解析算法。此算法由兩個階段組成:符號化及構建樹。
符號化是詞法分析的過程,將輸入內容解析成多個標記,HTML標記包括起始標記、結束標記、屬性名稱和屬性值。標記生成器識別標記,傳遞給樹構造器,而後讀取下一個字符以識別下一個標記,如此反覆直到輸入的結束。
該算法的輸出結果是HTML標記。該算法使用狀態機來表示。每個狀態接收來自輸入信息流的一個或多個字符,並根據這些字符更新下一個狀態。當前的標記化狀態和樹結構狀態會影響進入下一狀態的決定。這意味着,即便接收的字符相同,對於下一個正確的狀態也會產生不一樣的結果,具體取決於當前的狀態。
在建立解析器的同時,也會建立 Document 對象。在樹構建階段,以 Document 爲根節點的 DOM 樹也會不斷進行修改,向其中添加各類元素。標記生成器發送的每一個節點都會由樹構建器進行處理。規範中定義了每一個標記所對應的 DOM 元素,這些元素會在接收到相應的標記時建立。這些元素不只會添加到 DOM 樹中,還會添加到開放元素的堆棧中。此堆棧用於糾正嵌套錯誤和處理未關閉的標記。其算法也能夠用狀態機來描述。這些狀態稱爲「插入模式」。
樹構建階段的輸入是一個來自標記化階段的標記序列。第一個模式是「initial mode」。接收 HTML 標記後轉爲「before html」模式,並在這個模式下從新處理此標記。這樣會建立一個 HTMLHtmlElement 元素,並將其附加到 Document 根對象上。
而後狀態將改成「before head」。此時咱們接收「body」標記。即便咱們的示例中沒有「head」標記,系統也會隱式建立一個 HTMLHeadElement,並將其添加到樹中。
如今咱們進入了「in head」模式,而後轉入「after head」模式。系統對 body 標記進行從新處理,建立並插入 HTMLBodyElement,同時模式轉變爲「body」。
如今,接收由「Hello world」字符串生成的一系列字符標記。接收第一個字符時會建立並插入「Text」節點,而其餘字符也將附加到該節點。
接收 body 結束標記會觸發「after body」模式。如今咱們將接收 HTML 結束標記,而後進入「after after body」模式。接收到文件結束標記後,解析過程就此結束。
在此階段,瀏覽器會將文檔標註爲交互狀態,並開始解析那些處於「deferred」模式的腳本,也就是那些應在文檔解析完成後才執行的腳本。而後,文檔狀態將設置爲「完成」,一個「加載」事件將隨之觸發。
至少要可以處理如下錯誤狀況:
明顯不能在某些外部標記中添加的元素。在此狀況下,咱們應該關閉全部標記,直到出現禁止添加的元素,而後再加入該元素。
咱們不能直接添加的元素。這極可能是網頁做者忘記添加了其中的一些標記(或者其中的標記是可選的)。這些標籤可能包括:HTML HEAD BODY TBODY TR TD LI(還有遺漏的嗎?)。
向 inline 元素內添加 block 元素。關閉全部 inline 元素,直到出現下一個較高級的 block 元素。
若是這樣仍然無效,可關閉全部元素,直到能夠添加元素爲止,或者忽略該標記。
和HTML不一樣,CSS是上下文無關的語法,可使用簡介中描述的各類解析器進行解析。事實上,CSS 規範定義了 CSS 的詞法和語法。
Webkit 使用 Flex 和 Bison 解析器生成器,經過 CSS 語法文件自動建立解析器。正如咱們以前在解析器簡介中所說,Bison 會建立自下而上的移位歸約解析器。Firefox 使用的是人工編寫的自上而下的解析器。這兩種解析器都會將 CSS 文件解析成 StyleSheet 對象,且每一個對象都包含 CSS 規則。CSS 規則對象則包含選擇器和聲明對象,以及其餘與 CSS 語法對應的對象。
網絡的模型是同步的。網頁做者但願解析器遇到 <script>
標記時當即解析並執行腳本。文檔的解析將中止,直到腳本執行完畢。若是腳本是外部的,那麼解析過程會中止,直到從網絡同步抓取資源完成後再繼續。此模型已經使用了多年,也在 HTML4 和 HTML5 規範中進行了指定。做者也能夠將腳本標註爲「defer」,這樣它就不會中止文檔解析,而是等到解析結束才執行。HTML5 增長了一個選項,可將腳本標記爲異步,以便由其餘線程解析和執行。
Webkit 和 Firefox 都進行了這項優化。在執行腳本時,其餘線程會解析文檔的其他部分,找出並加載須要經過網絡加載的其餘資源。經過這種方式,資源能夠在並行鏈接上加載,從而提升整體速度。請注意,預解析器不會修改 DOM 樹,而是將這項工做交由主解析器處理;預解析器只會解析外部資源(例如外部腳本、樣式表和圖片)的引用。
另外一方面,樣式表有着不一樣的模型。理論上來講,應用樣式表不會更改 DOM 樹,所以彷佛沒有必要等待樣式表並中止文檔解析。但這涉及到一個問題,就是腳本在文檔解析階段會請求樣式信息。若是當時尚未加載和解析樣式,腳本就會得到錯誤的回覆,這樣顯然會產生不少問題。這看上去是一個非典型案例,但事實上很是廣泛。Firefox 在樣式表加載和解析的過程當中,會禁止全部腳本。而對於 Webkit 而言,僅當腳本嘗試訪問的樣式屬性可能受還沒有加載的樣式表影響時,它纔會禁止該腳本。
在 DOM 樹構建的同時,瀏覽器還會構建另外一個樹結構:渲染樹。這是由可視化元素按照其顯示順序而組成的樹,也是文檔的可視化表示。它的做用是讓您按照正確的順序繪製內容。
Firefox 將渲染樹中的元素稱爲「框架」。Webkit 使用的術語是呈現器或呈現對象。
呈現器知道如何佈局並將自身及其子元素繪製出來。
呈現器是和 DOM 元素相對應的,但並不是一一對應。非可視化的 DOM 元素不會插入渲染樹中,例如「head」元素。若是元素的 display 屬性值爲「none」,那麼也不會顯示在渲染樹中(可是 visibility 屬性值爲「hidden」的元素仍會顯示)。
有一些 DOM 元素對應多個可視化對象。它們每每是具備複雜結構的元素,沒法用單一的矩形來描述。例如,「select」元素有 3 個呈現器:一個用於顯示區域,一個用於下拉列表框,還有一個用於按鈕。若是因爲寬度不夠,文本沒法在一行中顯示而分爲多行,那麼新的行也會做爲新的呈現器而添加。
另外一個關於多呈現器的例子是格式無效的 HTML。根據 CSS 規範,inline 元素只能包含 block 元素或 inline 元素中的一種。若是出現了混合內容,則應建立匿名的 block 呈現器,以包裹 inline 元素。
有一些呈現對象對應於 DOM 節點,但在樹中所在的位置與 DOM 節點不一樣。浮動定位和絕對定位的元素就是這樣,它們處於正常的流程以外,放置在樹中的其餘地方,並映射到真正的框架,而放在原位的是佔位框架。
初始容器 block 爲「viewport」,而在 Webkit 中則爲「RenderView」對象。
在 Firefox 中,系統會針對 DOM 更新註冊展現層,做爲偵聽器。展現層將框架建立工做委託給 FrameConstructor,由該構造器解析樣式(請參閱樣式計算)並建立框架。
在 Webkit 中,解析樣式和建立呈現器的過程稱爲「附加」。每一個 DOM 節點都有一個「attach」方法。附加是同步進行的,將節點插入 DOM 樹須要調用新的節點「attach」方法。
處理 html 和 body 標記就會構建渲染樹根節點。這個根節點呈現對象對應於 CSS 規範中所說的容器 block,這是最上層的 block,包含了其餘全部 block。它的尺寸就是視口,即瀏覽器窗口顯示區域的尺寸。Firefox 稱之爲 ViewPortFrame,而 Webkit 稱之爲 RenderView。這就是文檔所指向的呈現對象。渲染樹的其他部分以 DOM 樹節點插入的形式來構建。
請參閱關於處理模型的 CSS2 規範。
構建渲染樹時,須要計算每個呈現對象的可視化屬性。這是經過計算每一個元素的樣式屬性來完成的。
樣式包括來自各類來源的樣式表、inline 樣式元素和 HTML 中的可視化屬性(例如「bgcolor」屬性)。其中後者將通過轉化以匹配 CSS 樣式屬性。
樣式表的來源包括瀏覽器的默認樣式表、由網頁做者提供的樣式表以及由瀏覽器用戶提供的用戶樣式表(瀏覽器容許您定義本身喜歡的樣式。以 Firefox 爲例,用戶能夠將本身喜歡的樣式表放在「Firefox Profile」文件夾下)。
樣式計算存在如下難點:
樣式數據是一個超大的結構,存儲了無數的樣式屬性,這可能形成內存問題。
若是不進行優化,爲每個元素查找匹配的規則會形成性能問題。要爲每個元素遍歷整個規則列表來尋找匹配規則,這是一項浩大的工程。選擇器會具備很複雜的結構,這就會致使某個匹配過程一開始看起來極可能是正確的,但最終發現實際上是徒勞的,必須嘗試其餘匹配路徑。
讓咱們來看看瀏覽器是如何處理這些問題的:
Webkit 節點會引用樣式對象 (RenderStyle)。這些對象在某些狀況下能夠由不一樣節點共享。這些節點是同級關係,而且:
這些元素必須處於相同的鼠標狀態(例如,不容許其中一個是「:hover」狀態,而另外一個不是)
任何元素都沒有 ID
標記名稱應匹配
類屬性應匹配
映射屬性的集合必須是徹底相同的
連接狀態必須匹配
焦點狀態必須匹配
任何元素都不該受屬性選擇器的影響,這裏所說的「影響」是指在選擇器中的任何位置有任何使用了屬性選擇器的選擇器匹配
元素中不能有任何 inline 樣式屬性
不能使用任何同級選擇器。WebCore 在遇到任何同級選擇器時,只會引起一個全局開關,並停用整個文檔的樣式共享(若是存在)。這包括 + 選擇器以及 :first-child 和 :last-child 等選擇器。
爲了簡化樣式計算,Firefox 還採用了另外兩種樹:規則樹和樣式上下文樹。Webkit 也有樣式對象,但它們不是保存在相似樣式上下文樹這樣的樹結構中,只是由 DOM 節點指向此類對象的相關樣式。
樣式上下文包含端值。要計算出這些值,應按照正確順序應用全部的匹配規則,並將其從邏輯值轉化爲具體的值。例如,若是邏輯值是屏幕大小的百分比,則須要換算成絕對的單位。規則樹的點子真的很巧妙,它使得節點之間能夠共享這些值,以免重複計算,還能夠節約空間。
全部匹配的規則都存儲在樹中。路徑中的底層節點擁有較高的優先級。規則樹包含了全部已知規則匹配的路徑。規則的存儲是延遲進行的。規則樹不會在開始的時候就爲全部的節點進行計算,而是隻有當某個節點樣式須要進行計算時,纔會向規則樹添加計算的路徑。
這個想法至關於將規則樹路徑視爲詞典中的單詞。若是咱們已經計算出以下的規則樹:
假設咱們須要爲內容樹中的另外一個元素匹配規則,而且找到匹配路徑是 B - E - I(按照此順序)。因爲咱們在樹中已經計算出了路徑 A - B - E - I - L,所以就已經有了此路徑,這就減小了如今所需的工做量。
讓咱們看看規則樹如何幫助咱們減小工做。
1)結構劃分
樣式上下文可分割成多個結構。這些結構體包含了特定類別(如 border 或 color)的樣式信息。結構中的屬性都是繼承的或非繼承的。繼承屬性若是未由元素定義,則繼承自其父代。非繼承屬性(也稱爲「重置」屬性)若是未進行定義,則使用默認值。
規則樹經過緩存整個結構(包含計算出的端值)爲咱們提供幫助。這一想法假定底層節點沒有提供結構的定義,則可以使用上層節點中的緩存結構。
2)使用規則樹計算樣式上下文
在計算某個特定元素的樣式上下文時,咱們首先計算規則樹中的對應路徑,或者使用現有的路徑。而後咱們沿此路徑應用規則,在新的樣式上下文中填充結構。咱們從路徑中擁有最高優先級的底層節點(一般也是最特殊的選擇器)開始,並向上遍歷規則樹,直到結構填充完畢。若是該規則節點對於此結構沒有任何規範,那麼咱們能夠實現更好的優化:尋找路徑更上層的節點,找到後指定完整的規範並指向相關節點便可。這是最好的優化方法,由於整個結構都能共享。這能夠減小端值的計算量並節約內存。
若是咱們找到了部分定義,就會向上遍歷規則樹,直到結構填充完畢。
若是咱們找不到結構的任何定義,那麼假如該結構是「繼承」類型,咱們會在上下文樹中指向父代的結構,這樣也能夠共享結構。若是是 reset 類型的結構,則會使用默認值。
若是最特殊的節點確實添加了值,那麼咱們須要另外進行一些計算,以便將這些值轉化成實際值。而後咱們將結果緩存在樹節點中,供子代使用。
若是某個元素與其同級元素都指向同一個樹節點,那麼它們就能夠共享整個樣式上下文。
在 Webkit 中沒有規則樹,所以會對匹配的聲明遍歷 4 次。首先應用非重要高優先級的屬性(因爲做爲其餘屬性的依據而應首先應用的屬性,例如 display),接着是高優先級重要規則,而後是普通優先級非重要規則,最後是普通優先級重要規則。這意味着屢次出現的屬性會根據正確的層疊順序進行解析。最後出現的最終生效。
所以歸納來講,共享樣式對象(整個對象或者對象中的部分結構)能夠解決問題 1和問題 3。Firefox 規則樹還有助於按照正確的順序應用屬性。
樣式對象具備每一個可視化屬性一一對應的屬性(均爲 CSS 屬性但更爲通用)。若是某個屬性未由任何匹配規則所定義,那麼部分屬性就可由父代元素樣式對象繼承。其餘屬性具備默認值。
若是定義不止一個,就會出現問題,須要經過層疊順序來解決。
1)樣式表層疊順序
某個樣式屬性的聲明可能會出如今多個樣式表中,也可能在同一個樣式表中出現屢次。這意味着應用規則的順序極爲重要。這稱爲「層疊」順序。根據 CSS2 規範,層疊的順序爲(優先級從低到高):
瀏覽器聲明
用戶普通聲明
做者普通聲明
做者重要聲明
用戶重要聲明
瀏覽器聲明是重要程度最低的,而用戶只有將該聲明標記爲「重要」才能夠替換網頁做者的聲明。一樣順序的聲明會根據特異性進行排序,而後再是其指定順序。HTML 可視化屬性會轉換成匹配的 CSS 聲明。它們被視爲低優先級的網頁做者規則。
2)特異性
選擇器的特異性由 CSS2 規範定義以下:
若是聲明來自於「style」屬性,而不是帶有選擇器的規則,則記爲 1,不然記爲 0 (= a)
記爲選擇器中 ID 屬性的個數 (= b)
記爲選擇器中其餘屬性和僞類的個數 (= c)
記爲選擇器中元素名稱和僞元素的個數 (= d)
將四個數字按 a-b-c-d 這樣鏈接起來(位於大數進制的數字系統中),構成特異性。
您使用的進製取決於上述類別中的最高計數。
例如,若是 a=14,您可使用十六進制。若是 a=17,那麼您須要使用十七進制;固然不太可能出現這種狀況,除非是存在以下的選擇器:html body div div p …(在選擇器中出現了 17 個標記,這樣的可能性極低)。
3)規則排序
找到匹配的規則以後,應根據級聯順序將其排序。Webkit 對於較小的列表會使用冒泡排序,而對較大的列表則使用歸併排序。
Webkit 使用一個標記來表示是否全部的頂級樣式表(包括 @imports)均已加載完畢。若是在附加過程當中還沒有徹底加載樣式,則使用佔位符,並在文檔中進行標註,等樣式表加載完畢後再從新計算。
呈現器在建立完成並添加到渲染樹時,並不包含位置和大小信息。計算這些值的過程稱爲佈局或重排。
HTML 採用基於流的佈局模型,這意味着大多數狀況下只要一次遍歷就能計算出幾何信息。處於流中靠後位置元素一般不會影響靠前位置元素的幾何特徵,所以佈局能夠按從左至右、從上至下的順序遍歷文檔。可是也有例外狀況,好比 HTML 表格的計算就須要不止一次的遍歷 。
座標系是相對於根框架而創建的,使用的是上座標和左座標。
佈局是一個遞歸的過程。它從根呈現器(對應於 HTML 文檔的 元素)開始,而後遞歸遍歷部分或全部的框架層次結構,爲每個須要計算的呈現器計算幾何信息。
根呈現器的位置左邊是 0,0,其尺寸爲視口(也就是瀏覽器窗口的可見區域)。
全部的呈現器都有一個「laybout」或者「reflow」方法,每個呈現器都會調用其須要進行佈局的子代的 layout 方法。
爲避免對全部細小更改都進行總體佈局,瀏覽器採用了一種「dirty 位」系統。若是某個呈現器發生了更改,或者將自身及其子代標註爲「dirty」,則須要進行佈局。
有兩種標記:「dirty」和「children are dirty」。「children are dirty」表示儘管呈現器自身沒有變化,但它至少有一個子代須要佈局。
全局佈局是指觸發了整個渲染樹範圍的佈局,觸發緣由可能包括:
影響全部呈現器的全局樣式更改,例如字體大小更改。
屏幕大小調整。
佈局能夠採用增量方式,也就是隻對 dirty 呈現器進行佈局(這樣可能存在須要進行額外佈局的弊端)。
當呈現器爲 dirty 時,會異步觸發增量佈局。例如,當來自網絡的額外內容添加到 DOM 樹以後,新的呈現器附加到了渲染樹中。
增量佈局是異步執行的。Firefox 將增量佈局的「reflow 命令」加入隊列,而調度程序會觸發這些命令的批量執行。Webkit 也有用於執行增量佈局的計時器:對渲染樹進行遍歷,並對 dirty 呈現器進行佈局。
請求樣式信息(例如「offsetHeight」)的腳本可同步觸發增量佈局。
全局佈局每每是同步觸發的。
有時,當初始佈局完成以後,若是一些屬性(如滾動位置)發生變化,佈局就會做爲回調而觸發。
若是佈局是由「大小調整」或呈現器的位置(而非大小)改變而觸發的,那麼能夠從緩存中獲取呈現器的大小,而無需從新計算。
在某些狀況下,只有一個子樹進行了修改,所以無需從根節點開始佈局。這適用於在本地進行更改而不影響周圍元素的狀況,例如在文本字段中插入文本(不然每次鍵盤輸入都將觸發從根節點開始的佈局)。
佈局一般具備如下模式:
父呈現器肯定本身的寬度。
父呈現器依次處理子呈現器,而且:
放置子呈現器(設置 x,y 座標)。
若是有必要,調用子呈現器的佈局(若是子呈現器是 dirty 的,或者這是全局佈局,或出於其餘某些緣由),這會計算子呈現器的高度。
父呈現器根據子呈現器的累加高度以及邊距和補白的高度來設置自身高度,此值也可供父呈現器的父呈現器使用。
將其 dirty 位設置爲 false。
Firefox 使用「state」對象 (nsHTMLReflowState) 做爲佈局的參數(稱爲「reflow」),這其中包括了父呈現器的寬度。
Firefox 佈局的輸出爲「metrics」對象 (nsHTMLReflowMetrics),其包含計算得出的呈現器高度。
若是呈現器在佈局過程當中須要換行,會當即中止佈局,並告知其父代須要換行。父代會建立額外的呈現器,並對其調用佈局。
在繪製階段,系統會遍歷渲染樹,並調用呈現器的「paint」方法,將呈現器的內容顯示在屏幕上。繪製工做是使用用戶界面基礎組件完成的。
和佈局同樣,繪製也分爲全局(繪製整個渲染樹)和增量兩種。在增量繪製中,部分呈現器發生了更改,可是不會影響整個樹。更改後的呈現器將其在屏幕上對應的矩形區域設爲無效,這致使 OS 將其視爲一塊「dirty 區域」,並生成「paint」事件。OS 會很巧妙地將多個區域合併成一個。在 Chrome 瀏覽器中,狀況要更復雜一些,由於 Chrome 瀏覽器的呈現器不在主進程上。Chrome 瀏覽器會在某種程度上模擬 OS 的行爲。展現層會偵聽這些事件,並將消息委託給呈現根節點。而後遍歷渲染樹,直到找到相關的呈現器,該呈現器會從新繪製本身(一般也包括其子代)。
Firefox 遍歷整個渲染樹,爲繪製的矩形創建一個顯示列表。列表中按照正確的繪製順序(先是呈現器的背景,而後是邊框等等)包含了與矩形相關的呈現器。這樣等到從新繪製的時候,只需遍歷一次渲染樹,而不用屢次遍歷(繪製全部背景,而後繪製全部圖片,再繪製全部邊框等等)。
Firefox 對此過程進行了優化,也就是不添加隱藏的元素,例如被不透明元素徹底遮擋住的元素。
在從新繪製以前,Webkit 會將原來的矩形另存爲一張位圖,而後只繪製新舊矩形之間的差別部分。
在發生變化時,瀏覽器會盡量作出最小的響應。所以,元素的顏色改變後,只會對該元素進行重繪。元素的位置改變後,只會對該元素及其子元素(可能還有同級元素)進行佈局和重繪。添加 DOM 節點後,會對該節點進行佈局和重繪。一些重大變化(例如增大「html」元素的字體)會致使緩存無效,使得整個渲染樹都會進行從新佈局和繪製。
渲染引擎採用了單線程。幾乎全部操做(除了網絡操做)都是在單線程中進行的。在 Firefox 和 Safari 中,該線程就是瀏覽器的主線程。而在 Chrome 瀏覽器中,該線程是標籤進程的主線程。
網絡操做可由多個並行線程執行。並行鏈接數是有限的(一般爲 2 至 6 個,以 Firefox 3 爲例是 6 個)。
事件循環
瀏覽器的主線程是事件循環。它是一個無限循環,永遠處於接受處理狀態,並等待事件(如佈局和繪製事件)發生,並進行處理。
根據 CSS2 規範,「畫布」這一術語是指「用來呈現格式化結構的空間」,也就是供瀏覽器繪製內容的區域。畫布的空間尺寸大小是無限的,可是瀏覽器會根據視口的尺寸選擇一個初始寬度。
根據 www.w3.org/TR/CSS2/zindex.html,畫布若是包含在其餘畫布內,就是透明的;不然會由瀏覽器指定一種顏色。
CSS 框模型描述的是針對文檔樹中的元素而生成,並根據可視化格式模型進行佈局的矩形框。
每一個框都有一個內容區域(例如文本、圖片等),還有可選的周圍補白、邊框和邊距區域。
每個節點都會生成 0..n 個這樣的框。 全部元素都有一個「display」屬性,決定了它們所對應生成的框類型。
默認值是 inline,可是瀏覽器樣式表設置了其餘默認值。例如,「div」元素的 display 屬性默認值是 block。
您能夠在這裏找到默認樣式表示例:www.w3.org/TR/CSS2/sample.html
有三種定位方案:
普通:根據對象在文檔中的位置進行定位,也就是說對象在渲染樹中的位置和它在 DOM 樹中的位置類似,並根據其框類型和尺寸進行佈局。
浮動:對象先按照普通流進行佈局,而後儘量地向左或向右移動。
絕對:對象在渲染樹中的位置和它在 DOM 樹中的位置不一樣。
定位方案是由「position」屬性和「loat」屬性設置的。
若是值是 static 和 relative,就是普通流
若是值是 absolute 和 fixed,就是絕對定位
static 定位無需定義位置,而是使用默認定位。對於其餘方案,網頁做者須要指定位置:top、bottom、left、right。
框的佈局方式是由如下因素決定的:
框尺寸
定位方案
外部信息,例如圖片大小和屏幕大小
框類型
block 框:造成一個 block,在瀏覽器窗口中擁有其本身的矩形區域。
block 採用的是一個接一個的垂直格式,而 inline 採用的是水平格式。
inline 框放置在行中或「行框」中。這些行至少和最高的框同樣高,還能夠更高,當框根據「底線」對齊時,這意味着元素的底部須要根據其餘框中非底部的位置對齊。若是容器的寬度不夠,inline 元素就會分爲多行放置。在段落中常常發生這種狀況。
相對定位:先按照普通方式定位,而後根據所需偏移量進行移動。
浮動框會移動到行的左邊或右邊。有趣的特徵在於,其餘框會浮動在它的周圍。
這種佈局是準肯定義的,與普通流無關。元素不參與普通流。尺寸是相對於容器而言的。在固定定位中,容器就是可視區域。
請注意,即便在文檔滾動時,固定框也不會移動。
這是由 z-index CSS 屬性指定的。它表明了框的第三個維度,也就是沿「z 軸」方向的位置。
這些框分散到多個堆棧(稱爲堆棧上下文)中。在每個堆棧中,會首先繪製後面的元素,而後在頂部繪製前面的元素,以便更靠近用戶。若是出現重疊,新繪製的元素就會覆蓋以前的元素。
堆棧是按照 z-index 屬性進行排序的。具備「z-index」屬性的框造成了本地堆棧。視口具備外部堆棧。
關鍵詞:
HTML: 超文本標記語言。「超文本」就是指頁面內能夠包含圖片、連接,甚至音樂、程序等非文字元素。
XML:可擴展標記語言。一種用於標記電子文件使其具備結構性的標記語言。
PDF:Portable Document Format的簡稱,意爲 便攜式文檔格式。
正則表達式(規則表達式):Regular Expression,簡寫爲regex、regexp或RE,計算機科學的一個概念。正則表一般被用來檢索、替換那些符合某個模式(規則)的文本。
BNF:Backus-Naur Form,意爲巴科斯範式,一種形式化符號來描述給定語言的語法。
DOM:Document Object Model,意爲文檔對象模型,處理可擴展標誌語言的標準編程接口。
腳本:是使用一種特定的描述性語言,依據必定的格式編寫的可執行文件,又稱做宏或批處理文件。
參考:http://blog.csdn.net/dangnian/article/details/50876241