整合營銷服務商

          電腦端+手機端+微信端=數據同步管理

          免費咨詢熱線:

          前端面試必知必會的十點,你都知道嗎?

          端面試必知必會的十點:


          前端面試必知必會的十點?你都知道嗎?


          1、常見的瀏覽器內核有哪些?

          IE瀏覽器的內核:Trident、

          Mozilla的Gecko、

          Chrome的Blink(WebKit的分支)、

          Opera內核原為Presto,現為Blink


          2、行內元素有哪些?塊級元素有哪些?CSS的盒模型?

          塊級元素:div p h1 h2 h3 h4 form ul

          行內元素: a b br i span input select

          Css盒模型:內容,border ,margin,padding


          3、簡述一下你對HTML語義化的理解?

          HTML語義化是指根據內容的結構化(內容語義化),選擇合適的標簽(代碼語義化)便于開發者閱讀和寫出更優雅的代碼的同時讓瀏覽器的爬蟲和機器很好地解析。

          HTML語義化的主要目的是:

          1).為了在沒有css的情況下,頁面也能呈現出很好地內容結構、代碼結構

          2).有利于用戶體驗

          3).有利于SEO和搜索引擎建立良好的溝通。

          4).方便其他設備解析以意義的方式來渲染網頁、

          5).便于團隊開發和維護,增加可讀性。


          4、前端頁面有哪三層構成,分別是什么?作用是什么?

          最準確的網頁設計思路是把網頁分成三個層次,即:結構層、表示層、行為層。

          網頁的結構層(structural layer)由 HTML 或 XHTML 之類的標記語言負責創建。標簽,也就是那些出現在尖括號里的單詞,對網頁內容的語義含義做出了描述,但這些標簽不包含任何關于如何顯示有關內容的信息。例如,P 標簽表達了這樣一種語義:“這是一個文本段。”

          網頁的表示層(presentation layer)由 CSS 負責創建。 CSS 對“如何顯示有關內容”的問題做出了回答。

          網頁的行為層(behavior layer)負責回答“內容應該如何對事件做出反應”這一問題。這是 Javascript 語言和 DOM 主宰的領域。


          5、HTML5有哪些新特性、移除了哪些元素?如何處理HTML5新標簽的瀏覽器兼容問題?如何區分 HTML 和HTML5?

          HTML5 現在已經不是 SGML 的子集,主要是關于圖像,位置,存儲,多任務等功能的增加。

          繪畫 canvas

          用于媒介回放的 video 和 audio 元素

          本地離線存儲 localStorage 長期存儲數據,瀏覽器關閉后數據不丟失;

          sessionStorage 的數據在瀏覽器關閉后自動刪除

          語意化更好的內容元素,比如 article、footer、header、nav、section

          表單控件,calendar、date、time、email、url、search

          新的技術webworker, websockt, Geolocation

          移除的元素

          純表現的元素:basefont,big,center,font, s,strike,tt,u;

          對可用性產生負面影響的元素:frame,frameset,noframes;

          支持HTML5新標簽:

          IE8/IE7/IE6支持通過document.createElement方法產生的標簽,

          可以利用這一特性讓這些瀏覽器支持HTML5新標簽,

          瀏覽器支持新標簽后,還需要添加標簽默認的樣式。


          6、請描述一下 cookies,sessionStorage 和 localStorage 的區別?

          cookie在瀏覽器和服務器間來回傳遞。 sessionStorage和localStorage不會

          sessionStorage和localStorage的存儲空間更大;

          sessionStorage和localStorage有更多豐富易用的接口;

          sessionStorage和localStorage各自獨立的存儲空間。


          7、CSS引入的方式有哪些? link和@import的區別是?

          內聯 內嵌 外鏈 導入

          區別 :同時加載

          前者無兼容性,后者CSS2.1以下瀏覽器不支持

          Link 支持使用javascript改變樣式,后者不可


          8、CSS清除浮動的幾種方法(至少兩種)

          使用帶clear屬性的空元素

          使用CSS的overflow屬性;

          使用CSS的:after偽元素;

          使用鄰接元素處理。


          9、談一談JavaScript作用域鏈

          當執行一段JavaScript代碼(全局代碼或函數)時,JavaScript引擎會創建為其創建一個作用域又稱為執行上下文(Execution Context),在頁面加載后會首先創建一個全局的作用域,然后每執行一個函數,會建立一個對應的作用域,從而形成了一條作用域鏈。每個作用域都有一條對應的作用域鏈,鏈頭是全局作用域,鏈尾是當前函數作用域。

          作用域鏈的作用是用于解析標識符,當函數被創建時(不是執行),會將this、arguments、命名參數和該函數中的所有局部變量添加到該當前作用域中,當JavaScript需要查找變量X的時候(這個過程稱為變量解析),它首先會從作用域鏈中的鏈尾也就是當前作用域進行查找是否有X屬性,如果沒有找到就順著作用域鏈繼續查找,直到查找到鏈頭,也就是全局作用域鏈,仍未找到該變量的話,就認為這段代碼的作用域鏈上不存在x變量,并拋出一個引用錯誤(ReferenceError)的異常。


          10、如何理解JavaScript原型鏈

          JavaScript中的每個對象都有一個prototype屬性,我們稱之為原型,而原型的值也是一個對象,因此它也有自己的原型,這樣就串聯起來了一條原型鏈,原型鏈的鏈頭是object,它的prototype比較特殊,值為null。

          原型鏈的作用是用于對象繼承,函數A的原型屬性(prototype property)是一個對象,當這個函數被用作構造函數來創建實例時,該函數的原型屬性將被作為原型賦值給所有對象實例,比如我們新建一個數組,數組的方法便從數組的原型上繼承而來。

          當訪問對象的一個屬性時, 首先查找對象本身, 找到則返回; 若未找到, 則繼續查找其原型對象的屬性(如果還找不到實際上還會沿著原型鏈向上查找, 直至到根). 只要沒有被覆蓋的話, 對象原型的屬性就能在所有的實例中找到,若整個原型鏈未找到則返回undefined。

          本文很長,但值得看,完全搞懂瀏覽器)

          1. 介紹

          1.1 示例瀏覽器

          主流瀏覽器:Internet Explorer, Firefox, Safari, Chrome and Opera

          示例瀏覽器:Firefox、Chrome(開源)和 Safari(部分開源)

          瀏覽器使用統計:http://gs.statcounter.com/

          1.2 瀏覽器的主要功能(The browser's main functionality)

          瀏覽器的用戶界面:

          • 輸入 URI 的地址欄
          • 回退、前進按鈕
          • 網址收藏
          • 刷新和停止按鈕
          • 首頁按鈕

          HTML5 規范列出了瀏覽器的幾種通用元素:

          • 地址欄
          • 狀態欄
          • 工具欄

          1.3 瀏覽器的高層架構(The browser's high level structure)

          瀏覽器的組成部分:

          • 用戶界面

          包含地址欄、回退和前進按鈕、網址收藏等。除了展示請求到的資源的主窗口,其他都是用戶界面。

          • 瀏覽器引擎

          請求和操作渲染引擎的接口

          • 渲染引擎

          負責展示請求的資源。比如說,請求到的資源是 HTML,渲染引擎就負責解析 HTML 和 CSS,然后將解析后的內容展示在屏幕上。

          • 網絡

          網絡調用,如 HTTP 請求。它有跨平臺的獨立接口,且每個平臺都有自己的底層實現方式。

          • 用戶界面后端

          繪制基本的窗體小部件,如組合框、視窗。它顯示一個非平臺特定的泛型界面。在底層,它使用操作系統的用戶界面方法。

          • JS 解析器

          解析和執行 JS 代碼

          • 數據存儲

          持久層。瀏覽器需要它來存儲硬盤上的各種數據,如 cookies。新的 HTML 規范(HTML5)定義了 網頁數據庫 —— 瀏覽器中的完整(輕量)的數據庫。

          瀏覽器主要組成部分示意圖

          值得注意的是,chrome 瀏覽器有多個渲染引擎實例——每個 tab 標簽頁一個每個 tab 標簽頁都是一個獨立的進程。(大家用Chrome就錯不了!!!)

          2. 渲染引擎

          2.1 渲染引擎(The rendering engine)

          渲染引擎負責渲染,將請求到的內容呈現到屏幕上。

          默認情況下,渲染引擎可顯示 HTML、XML文檔和圖片。也可通過插件(瀏覽器擴展程序)顯示其他類型。比如,可通過 PDF 閱讀器插件顯示 PDF 文件。

          2.2 主流程(The main flow)

          渲染引擎一開始從網絡層請求文檔內容。

          接下來是以下的基本流程:

          渲染引擎先解析 HTML,將標簽轉換成樹的節點——叫作"內容樹"。然后解析樣式數據,包括外聯樣式和內聯樣式。樣式信息和 HTML 中的可視指令一起用于創建另一棵樹——渲染樹。

          渲染樹包含具有可視屬性(如顏色和尺寸)的矩形。矩形按正確的順序顯示在屏幕上。

          渲染樹構建完成后,就進入"布局"流程。也就是給每個節點分配正確的坐標——節點應該顯示在屏幕上的哪個位置。下一步就是繪制——將遍歷渲染樹,通過用戶界面(UI)后端層繪制每個節點。

          重要的是要理解這是一個循序漸進的過程。為了更好的用戶體驗,渲染引擎將盡可能快地在屏幕上呈現內容。它不會等到所有的 HTML 都解析完成才開始構建和布局渲染樹。當程序還在持續處理來自網絡層的內容時,它已經解析和呈現了部分內容。

          2.3 主流程示例(Main flow examples)

          Webkit 主流程:

          Mozilla Gecko 渲染引擎主流程:

          2.4 解析和 DOM 樹構建(Parsing and DOM tree construction)

          2.4.1 解析(Parsing-General)

          由于解析是渲染引擎中非常重要的一個進程,所以我們會稍微深入講一下。首先介紹下解析。

          解析文檔就是將文檔轉換成某種有意義的——對代碼來說可以理解和使用的——結構。解析的結果通常是代表文檔結構的節點樹。通常叫作解析樹或語法樹。

          例如,解析 2 + 3 - 1,將返回如下樹:

          數學表達式樹節點:

          2.4.1.1 語法(Grammars)

          解析基于文檔所遵循的語法規則——編寫它的語言或形式。每種可編譯的形式必須包含確定的語法——由詞匯和語法規則組成。這叫作 上下文無關語法。人類語言并不是這樣的,所以不能通過傳統的解析技術來解析。

          2.4.1.2 詞法分析器(Parser-Lexer combination)

          解析可分為兩個子進程——詞法分析和語法分析。

          詞法分析就是將輸入內容分解成詞法單元的過程。詞法單元就是語言詞匯——有效構建塊的集合。在人類語言中,詞法單元由那種語言的詞典中出現的單詞所組成。

          語法分析就是語言的語法規則的應用。

          解析器通常將工作分配給兩個不同的部件——詞法分析器(有時也叫分詞器),負責將輸入內容分解成有效的詞法單元;解析器,負責根據語法規則分析文檔結構來構建解析樹,

          詞法分析器知道如何丟棄無關的字符,如空格和換行。

          從源文檔到解析樹的過程

          解析的過程是不斷重復的。解析器通常會向詞法分析器查詢一個新的詞法單元,然后將這個詞法單元與語法規則匹配,如果匹配到了,就將與這個詞法單元對應的節點添加到解析樹,然后繼續查詢下一個詞法單元。

          如果匹配不到,解析器會將這個詞法單元儲存起來,繼續查詢其他詞法單元,直到找到一個能匹配所有儲存起來的詞法單元的語法規則為止。如果找不到匹配規則,解析器將拋出異常。這就是說文檔是無效的,且包含了語法錯誤。

          2.4.1.3 翻譯(Translation)

          很多時候,解析樹并不是最終的產物。解析通常用于翻譯——將輸入文檔轉換成另一種形式。比如說編譯。編譯器——將源代碼轉換成機器代碼——首先會將它轉換成解析樹,然后再將解析樹翻譯成機器代碼文檔。

          編譯流程

          2.4.1.4 解析舉例(Parsing example)

          數學表達式樹節點 圖中,已經通過數學表達式構建過一個解析樹。現在定義一個簡單的數學語言,然后看一下解析過程。

          詞匯:這個語言可以包含整數、加號和減號。

          語法:

          • 語法構建塊是表達式、術語、操作。
          • 這個語言可包含任何數量的表達式。
          • 一個表達式被定義為:一個術語后面跟著一個操作,操作后面跟著另一個術語。
          • 一個操作就是一個加號或一個減號
          • 一個術語是一個整數或一個表達式。

          現在來分析下 "2 + 3 - 1"。

          第一個匹配到規則的子串是 "2",根據規則 5,它是一個術語。 第二個匹配到的是 "2 + 3",匹配到第三條規則——一個術語后面跟著一個操作,操作后面跟著另一個術語。 下一個匹配只能是末尾了。"2 + 3 - 1" 是一個表達式,因為我們已經知道 ?2+3? 是一個術語,所以我們有一個術語后面跟著一個操作,后面跟著另一個術語。 "2 + +" 不能匹配任何規則,所以是無效的輸入。

          2.4.1.5 詞匯和語法的正式定義(Formal definitions for vocabulary and syntax)

          詞匯通常由正則表達式表示。

          比如,上面的語言可以這樣定義:

          整數(INTEGER) : 0|[1-9][0-9]* 
          加法(PLUS) : + 
          減法(MINUS) : - 
          

          可以看到,整數是由正則表達式來表示的。

          語法通常用一種叫做 BNF 的形式來定義。上面的語言可以這樣定義:

          表達式(expression) := 術語(term) 操作(operation) 術語(term)
          操作(operation) := 加法(PLUS) | 減法(MINUS)
          術語(term) := 整數(INTEGER)| 表達式(expression)
          

          我們說過,如果一種語言的語法是上下文無關的,就能被常規解析器解析。

          上下文無關語法的一種直觀定義就是能完全用 BNF 形式表示。

          上下文無關語法的正式定義可參考:http://en.wikipedia.org/wiki/Context-free_grammar

          2.4.1.6 解析器種類(Types of parsers)

          解析器有兩種基本類型——自上而下的解析器和自下而上的解析器。

          直接的解釋就是:自上而下的解析器查找語法的高層結構,然后嘗試匹配其中的一個。自下而上的解析器從輸入開始,然后逐步地轉換成語法規則,從低級規則開始,直到匹配到高級規則為止。

          現在來看下這兩種解析器如何解析我們的示例。

          自上而下的解析器從高級規則開始,它將 "2 + 3" 定義成一個表達式,然后將 "2 + 3 - 1" 定義成一個表達式(表達式定義的過程逐步演化成匹配其他規則,但是起點是高級規則)。

          自下而上的解析器會掃描輸入內容,直到找到匹配的規則,然后用這個規則去替換匹配的內容。這個過程將一直持續到輸入內容的末尾。部分匹配的表達式儲存在解析的堆內存中。

          自下而上的解析器也叫作移位解析器。因為輸入內容被移到右邊(想象一個指示器從輸入內容開始不斷移到末尾),然后逐漸地變成語法規則。

          2.4.1.7 自動化解析器(Generating parsers automatically)

          有些工具可以自動生成解析器,叫作解析器生成器。給解析器生成器提供語言的語法——詞匯和語法規則——它就能生成一個工作解析器。

          創建解析器需要深入理解解析,而且手動創建一個優化的解析器也非常不易,所以解析器生成器就非常有用了。

          Webkit 使用兩種眾所周知的解析器生成器——Flex,用于創建分詞器;Bison,用于創建解析器(有時候也可能叫做 Lex 和 Yacc)。

          Flex 輸入是一個包含用正則表達式定義標記的文件。Bison 輸入是語言的語法規則和 BNF 形式。

          2.4.2 HTML 解析器(HTML Parser)

          HTML 解析器負責將 HTML 標記解析成解析樹。

          2.4.2.1 HTML 語法定義(The HTML grammar definition)

          HTML 的詞匯和語法規則由 W3C 組織的規范所定義。

          2.4.2.2 非上下文無關語法(Not a context free grammar)

          在介紹解析時,提到了語法能通過 BNF 的形式定義。

          然而,所有的傳統解析器都不適用于 HTML。HTML 不能輕易地由解析器所需的上下文無關語法定義。

          定義 HTML 語法有正式的形式——DTD(Document Type Definition 文檔類型聲明)。但是它不是一種上下文無關語法。

          初看之下這非常得奇怪。HTML 非常接近于 XML。有許多的 XML 解析器。還有一種 HTML 的變種——XHTML。所以差異在哪里?

          差異在于,HTML 非常的"寬容",它允許你省略隱式添加的標簽,有時還能省略開始或結束的標簽。基本上,它是一種"軟"語法,相反于 XML 的嚴格高要求的語法。

          表面上看起來是很小的差異,然而卻是天差地別。

          一方面,這也是為什么 HTML 如此受歡迎——它會原諒你的錯誤,使網站作者感到更加舒適。另一方面,這使得寫范式語法非常得困難。

          總得來說,HTML 不能輕易地被傳統解析器解析,因為它的語法不是上下文無關語法,也不能被 XML 解析器解析。

          HTML 是用 DTD 形式定義的。這種形式用來定義 SGML (標準通用標記語言)語言。 這種形式包含了所有允許的元素的定義,它們的屬性和層級。之前已經看到,HTML 文檔類型聲明不會形成上下文無關語法。

          DTD 有很多不同的類型。嚴格模式是唯一符合規范的,其他形式還包含了對瀏覽器過去使用的一些標簽的支持。目的是向后兼容舊的內容。

          2.4.2.4 DOM(文檔對象模型)

          輸出樹——解析樹,就是 DOM 元素和屬性節點的樹。DOM 是 Document Object Model 的縮寫。DOM 是 HTML 文檔的對象呈現,也是 HTML 元素與外界(如 JavaScript)的連接接口。

          樹的根節點是 Document(文檔)。

          DOM 和 標記有著幾乎一對一的關系。例如:

          <html>
           <body>
           <p>Hello World.</p>
           <div><img src="example.png"/></div>
           </body>
          </html>
          

          上面內容會被轉換成:

          像 HTML 一樣,DOM 由 W3C 組織規定。

          2.4.2.5 解析算法(The parsing algorithm)

          如之前所說,HTML 不能被常規的自上而下或自下而上的解析器解析。

          原因如下:

          • 語言的寬容特性
          • 瀏覽器具有傳統的容錯能力,以支持眾所周知的無效 HTML 案例。
          • 解析過程是可重入的(reentrant)。通常,在解析過程中,源是不會改變的,但是在 HTML 中,腳本元素包含 "document.write" 可以增加額外的標簽,所以解析過程實際改變了輸入的內容。

          不能使用傳統的解析技術,瀏覽器創建了自定義的解析 HTML 的解析器。

          解析算法由 HTML5 規范詳細描述。算法包含了兩個階段——分詞和樹的構建。

          分詞就是詞法分析,將輸入解析成標記。在 HTML 中,標記就是開始標簽、結束標簽、屬性名稱和屬性值。

          分詞器識別標記,把它給到樹構建器,然后繼續查找下一個字符,識別下一個標記,直到輸入的結束。

          HTML 解析流程:

          2.4.2.6 分詞算法(The tokenization algorithm)

          這個算法的輸出是一個 HTML 標記。這個算法表現為一個狀態機。每個狀態消耗輸入了的一個或多個字符,然后根據這些字符更新下一個狀態。

          這個決定受分詞狀態和樹的構建狀態所影響。這個算法太復雜,所以不能詳細講解,我們來看一個簡單的例子,大致地了解下。

          分詞下面的 HTML:

          <html>
           <body>
           Hello world.
           </body>
          </html>
          

          初始的狀態是 "數據狀態"。當遇到 "<" 字符時,狀態就變成 "標簽打開狀態"。遇到 "a-z" 字符時,會創建 "開始標簽標記",狀態就變成 "標簽名稱狀態"。 直到遇到 ">" 這個字符時,這個狀態才會結束。每個字符都被添加到這個標記名稱下。在我們的例子中,創建的標記就是 "html"。

          當遇到 ">" 時,當前的標記會被釋放,然后狀態變回 "數據狀態"。"<body>" 標簽也用相同的步驟來解析。現在,"html" 和 "body" 標記都已被釋放,狀態變回 "數據狀態"。 遇到 "Hello world." 的 "H" 時,會創建和釋放一個字符標記。這個過程持續到遇到 "</body>" 的 "<" 字符為止,此時會釋放 "Hello world." 的所有字符。

          現在又回到了 "標簽打開狀態"。遇到 "/" 時會創建 "結束標簽標記",然后變成 "標簽名稱狀態"。同樣的,這個狀態持續到遇到 ">" 這個字符為止。 然后這個新的標簽標記會放釋放,再次回到 "數據狀態"。"</html>" 標簽也會用同樣的方式解析。

          2.4.2.7 樹的構建算法(Tree construction algorithm)

          當解析器創建好時,Document 對象也創建好了。在樹的構建階段,會改變包含 Document 根節點的 DOM 樹,還會添加元素到 DOM 樹。每個被分詞器釋放的節點都將被樹構建器加工。 對于每個標記,規范會定義與它相對應的 DOM 元素,并且為該元素創建這個 DOM 元素。除了將元素添加到 DOM 樹中外,還會將元素添加到一個開放元素的堆中。這個堆用于修正嵌套錯誤和未關閉的元素。 構建算法也是通過狀態機的形式表示的。這些狀態叫作"嵌入模式"。

          來看一個樹構建的過程:

          <html>
           <body>
           Hello world.
           </body>
          </html>
          

          樹構建階段的輸入是一系列來自分詞階段的標記。第一個模式是 "初始模式"(initial mode)。接收 html 標記時,模式會轉變成 "html 前"(before html)模式,并會對這個模式下的標記進行回收。 此時,會創建 HTMLHtmlElement 元素,并添加到 根文檔對象(root Document object) 中。

          之后又會轉變成 "head 前"(before head) 狀態。當遇到 "body" 標記時,HTMLHeadElement 元素會被隱式地創建,并被添加到 DOM 樹,雖然我們沒有 "head" 這個標記。

          現在我們已經到了 "head 前" 模式,然后要進入到 "head 后"(after head) 模式。 現在對body 標記進行再加工,創建和插入 HTMLBodyElement 元素,此時又轉變成了 "body 內"(in body)模式。 現在接收 "Hello world." 這個字符串字符標記,第一個字母會使構建器創建和插入一個 文本節點(Text node),其余的字符會被添加到這個節點中。

          接收 body 的結束標記時,會轉變成 "body 后" (after body)模式。 之后會接收 html 的結束標記,此時會轉變成 "body 后后"(after after body) 模式。 接收到文件的結束標記時,解析也就結束了。

          html 的樹構建:

          2.4.2.8 解析結束的操作(Actions when the parsing is finished)

          在這個階段,瀏覽器會標記文檔是可交互的,然后開始解析"延遲"模式(deferred mode)下的腳本——在文檔解析完成后執行。此時,文檔狀態變成 "完成",拋出一個 "load" 事件。

          關于完整的 HTML5 規范的分詞和樹構建的算法,可參考:http://www.w3.org/TR/html5/syntax.html#html-parser

          2.4.2.9 瀏覽器容錯(Browsers error tolerance)

          在 HTML 頁面上,永遠不會出現 "無效語法"(Invalid Syntax)的錯誤。瀏覽器會修正它,然后繼續運行。

          比如下面這段 HTML 代碼:

          <html>
           <mytag>
           </mytag>
           <div>
           <p>
           </div>
           Really lousy html.
           </p>
          </html>
          

          我肯定已經違反了大概100萬條規則("mytag" 不是一個標準的標簽,錯誤的 "div" 和 "p" 元素嵌套等),但是瀏覽器仍然正確的顯示,沒有任何抱怨。所以許多的解析器代碼都在修正 HTML 作者的錯誤。

          不同瀏覽器的錯誤處理出奇得相當一致,雖然現有的 HTML 規范并未對此有規定。像書簽、前進/后退按鈕,就是瀏覽器多年發展以來的產物。

          有許多周知的無效代碼結構,它們在許多網站不斷重復,瀏覽器會嘗試用與其他瀏覽器一致的方式修正它們。

          HTML5 規范確實也有定義其中的一些要求。Webkit 在 HTML 解析器種類(HTML parser class)的開頭的注釋中,很好地總結了這些規范:

          解析器將分詞的輸入解析成文檔,構建文檔樹。如果這個文檔結構良好,那么解析文檔是很簡單的。然而,我們不得不處理許多結構不是很好的文檔,所以解析器不得不容錯。 我們得至少注意以下這些錯誤條件:

          • 1、明確禁止在外部標簽中添加的元素
          • 在這種情況下,我們應該閉合這些禁止添加元素的標簽,然后再添加。
          • 2、不允許直接添加元素
          • 這可能是這樣的,寫文檔的人忘記了一些中間的標簽(或者這些中間標簽是可選的)。
          • 這些標簽可能是:HTML BODY TBODY TR TD LI (還有嗎?)
          • 3、將塊級元素嵌套在行內元素中。閉合所有的塊級元素旁的行內元素。
          • 4、如果上面的都沒用,閉合所有的元素,除非允許添加元素和忽略標簽。

          現在來看一些 Webkit 容錯的例子:

          使用 </br> 代替 <br>

          有些網站使用 </br> 代替 <br>,為了兼容 IE 和 火狐,Webkit 會像下面這樣對待它:

          if (t->isCloseTag(brTag) && m_document->inCompatMode()) {
           reportError(MalformedBRError);
           t->beginTag = true;
          }
          

          注意:這些錯誤處理是在內部進行的,不會對用戶顯示。

          混亂的表格

          混亂的表格,就是一個表格嵌套在另一個表格中,但是沒有嵌套在表格的單元格中。

          例如:

          <table>
           <table>
           <tr>
           <td>inner table</td>
           </tr>
           </table>
           <tr>
           <td>outer table</td>
           </tr>
          </table>
          

          Webkit 會將這個層級變成兩個兄弟表格。

          <table>
           <tr>
           <td>outer table</td>
           </tr>
          </table>
          <table>
           <tr>
           <td>inner table</td>
           </tr>
          </table>
          

          代碼如下:

          if (m_inStrayTableContent && localName == tableTag)
           popBlock(tableTag);
          

          Webkit 將當前元素內容存在堆中,它會將內部的表格從外部的表格中推出。現在兩個表格變成了兄弟表格。

          嵌套表單元素

          如果用戶將一個表單嵌套在另一個表單中,第二個表單會被忽略掉。

          代碼:

          if (!m_currentFormElement) {
           m_currentFormElement = new HTMLFormElement(formTag, m_document);
          }
          

          很深的標簽層級

          看下面的注釋,不言而喻:

          html 或 body 閉合標簽的位置放錯

          再來看一下它的注釋:

          支持破碎的 html 我們不會閉合 body 標簽,因為一些愚蠢的網頁會在文檔結束時閉合它。 我們依靠 end() 方法來閉合。

          if (t->tagName == htmlTag || t->tagName == bodyTag )
           return;
          

          所有網頁開發者要注意了,除非你想你的代碼出現在 Webkit 容錯示例中,否則就寫結構良好的文檔。

          2.4.3 CSS 解析(CSS parsing)

          還記得什么是解析的概念嗎?不像 HTML,CSS 是上下文無關語法,可以通過之前介紹的解析器解析。 事實上,CSS 的詞法語法和句法語法由 CSS 規范規定,參考:https://www.w3.org/TR/CSS2/grammar.html

          來看一些例子:

          詞法語法(詞匯)是通過正則來定義每個標記的。

          "ident" 是 "identity" 的縮寫,比如一個類名(class name)。 "name" 是一個元素 id(由 # 來引用)。

          句法語法由 BNF 來描述。

          解釋:ruleset 的結構如下:

          div.error, a.error {
           color:red;
           font-weight:bold;
          }
          

          div.error 和 a.error 是選擇器。大括號內的內容會應用到這個 ruleset 上。這個結構會用下面的方式定義:

          ruleset
           : selector [ ',' S* selector ]*
           '{' S* declaration [ ';' S* declaration ]* '}' S*
           ;
          

          這就是說,一個 ruleset 就是一個選擇器或者多個由逗號和空格分隔的選擇器(S 代表空格)。一個 ruleset 包含一個大括號,以及括號內的一個或多個由分號分隔的聲明。 "聲明"(declaration)和 "選擇器"(selector)由下面的 BNF 定義。

          2.4.3.1 Webkit CSS 解析器(Webkit CSS parser)

          Webkit 使用 Flex 和 Bison 解析器生成器從 CSS 語法文件中自動生成解析器。

          回憶一下上面對解析器的介紹一節,Bison 創建一個自下而上的移位遞減解析器。

          Firefox 使用手寫的自上而下的解析器。

          在兩種情況下,CSS 文件都會被解析成 樣式表(StyleSheet)對象,每個對象包含 CSS 規則。每個 CSS 規則包含選擇器、聲明對象和其他與 CSS 語法相對應的對象。

          解析 CSS:

          2.4.4 解析腳本(Parsing scripts)

          本章主要會通過 JavaScript 來講解。

          2.4.5 處理腳本和樣式表的順序(The order of processing scripts and style sheets)

          2.4.5.1 腳本(Scripts)

          網絡模型是同步的。作者們都希望解析器遇到 <script> 標簽時能立即解析和執行。文檔解析會處于被阻塞狀態,直到腳本執行完畢。

          如果是外部腳本,那么先得從網上獲取資源——這也是同步的,文檔解析會處于被阻塞狀態,直到資源獲取完畢。

          多年來,一直都是這種模型,也是 HTML4 和 HTML5 規范規定的模型。作者可以將腳本標記為"延遲"(defer),這樣不會阻塞文檔的解析,解析完成后,就會執行。 HTML5 增加了一個選項,可以把腳本標記成異步的,這樣就可以在另一個線程中進行解析和執行。

          2.4.5.2 預解析(Speculative parsing)

          Webkit 和 Firefox 都做了這種優化。在執行腳本的過程中,另一個線程解析文檔的剩余部分,找出還需要從網上加載哪些資源,然后加載它們。 這樣,資源可以平行加載,總體速度也會更快。

          注意:預解析器不會改變 DOM 樹,DOM 樹還是會留給主解析器,它只會解析外部資源的引用,如外部腳本、樣式和圖片等。

          2.4.5.3 樣式表(Style sheets)

          樣式表有著不同的模型。概念上,看起來樣式表不會變成 DOM 樹,所以沒必要等待和阻塞文檔解析。 但是,在文檔解析階段,腳本會向樣式信息問一個問題。如果樣式還沒加載和解析,腳本會得到錯誤的答案,顯然,這會導致很多的問題。 這似乎是一個邊界情況,但是卻相當普遍。

          當有樣式在加載和解析時,Firefox 會阻塞所有的腳本。Webkit 僅會阻塞這些試圖獲取特定的樣式屬性——這些屬性可能會受未加載的樣式影響——的腳本。

          2.5 渲染樹構建(Render tree construction)

          構建 DOM 樹的同時,瀏覽器還在構建另一個樹——渲染樹。渲染樹由可視元素組成,這些元素按將要展示的順序排列。它是文檔的視覺呈現。 渲染樹的目的是保證內容有序繪制。

          Firefox 把渲染樹中的元素叫作 "幀"(frames)。

          Webkit 把這些元素叫作 "渲染器"(renderer)或 "渲染對象"(render object)。

          一個渲染器知道如何布局和繪制自身及其子類。

          Webkit 的渲染器對象的基本類—— RenderObject 類的定義如下:

          每個渲染器代表了一個矩形區域,與 CSS2 規范定義的節點的 CSS 盒子(盒模型)相對應。它包含了幾何信息,如寬、高和位置。

          盒子的類型由與節點相對應的 "display" 樣式屬性決定。

          下面的是 Webkit 的代碼,通過 "display" 屬性決定應該為 DOM 節點生成何種渲染器。

          還考慮了元素類型,如表單控件和表格有特殊的幀。

          在 Webkit 中,如果一個元素想創建一個特殊的渲染器,那么它會覆寫 createRenderer 這個方法。這些渲染器指向包含了非幾何信息的樣式對象。

          2.5.1 渲染樹與 DOM 樹的關系(The render tree relation to the DOM tree)

          渲染器與 DOM 元素相對應,但并不是一對一的關系。非可視元素不會被插入到渲染樹,比如 head 元素。 還有 display 屬性設置為 none 的元素也不會出現在渲染樹中(visibility 設置為 hidden 的元素會出現在渲染樹中)。

          有些 DOM 元素會對應多個可視對象。有些元素結構比較復雜,所以不能用單個矩形來表示。比如,select 元素有3個渲染器,一個渲染區域,一個渲染下拉列表框,一個渲染按鈕。 當文本在一行內顯示不下,被拆分成多行時,新行中的文本會被添加到新的渲染器中。

          另一個多個渲染器的例子是拆分的 HTML。CSS 規范規定一個行內元素只能包含塊級元素或只能包含行內元素。 如果行內元素既包含了行內元素又包含了塊級元素,會創建一個匿名塊級渲染器包裹這些行內元素。

          有些渲染對象與 DOM 節點一對一對應,但是在樹中的位置卻不同。浮動和絕對定位的元素脫離了流,放置在樹的不同位置,然后映射到實際的幀。它們存在于占位符幀中。

          渲染樹和與之對應的 DOM 樹,Viewport 是初始的包含塊。在 Webkit中,Viewport 是 RenderView 對象。

          2.5.2 構建樹的過程(The flow of constructing the tree)

          在 Firefox 中,構建過程表現為為 DOM 的更新注冊一個監聽器(listener),然后將幀的創建委派給 "幀構建器",構建器會分解樣式(見下面的樣式計算),創建幀。

          在 Webkit 中,分解樣式和創建渲染器的過程叫作 "附著"(attachment)。每個 DOM 節點都有一個 attach 方法。"附著" 是同步的,節點插入到 DOM 樹中會調用新節點的 attach 方法。

          渲染樹根節點構建時會處理 html 和 body 標簽。根渲染對象與 CSS 規范中的包含塊——最頂端的包含所有其他塊的塊——相對應。 包含塊的大小就是視口大小——瀏覽器窗口展示的區域大小。Firefox 把它叫做 "視口幀"(ViewPortFrame),Webkit 把它叫做 "RenderView"(渲染視口)。 這就是文檔指向的渲染對象。樹的其他部分被構建為 DOM 節點插入。

          CSS2 關于這個話題的的資料: http://www.w3.org/TR/CSS21/intro.html#processing-model

          2.5.3 樣式計算(Style Computation)

          構建渲染樹需要計算每個渲染對象的可視屬性。通過計算每個元素的樣式屬性來完成。

          樣式包含了不同來源的樣式表,如內聯樣式元素、HTML 中的可視屬性(比如 bgcolor 屬性)。后者被轉換,匹配 CSS 的樣式屬性。

          樣式表來源于瀏覽器的默認樣式表、網頁作者提供的樣式表、用戶樣式表——這些樣式表由瀏覽器用戶提供(瀏覽器允許用戶定制喜歡的風格,比如,在 Firefox 中,可以在 Firefox fold 中放置一份樣式表即可)。

          樣式計算帶來了一些難題

          • 1、樣式數據的結構龐大,包含了許多的樣式屬性,可能會引起內存問題。
          • 2、如果沒有優化,那么為每個元素查找匹配規則會導致性能問題。為每個元素查找匹配遍歷整個規則表是一項繁重的任務。 選擇器可以有復雜的結構,這會導致匹配過程會從看上去有希望的路徑開始匹配,而實際上卻是無效的,然后再去嘗試新的匹配路徑。
          • 3、應用規則涉及非常復雜的級聯規則,這些規則定義了規則的層次結構。

          比如下面的復合選擇器:

          div div div div{
           // ...
          }
          

          上面的代碼意思就是匹配一個 <div>,它是三個 <div> 的后代。 假如你想驗證這個匹配規則是否適用于某個 <div> 元素,你從樹上選某個路徑開始驗證,你需要遍歷節點樹去找到三個 <div>,結果卻只找到兩個,匹配規則并不生效。 你就得從節點樹的另一個路徑開始查找。

          • 應用規則涉及非常復雜的級聯規則,這些規則定義了規則的層次結構。

          我們來看看瀏覽器怎么解決這些問題:

          2.5.3.1 共享樣式數據(Sharing style data)

          Webkit 節點引用樣式對象(渲染樣式 RenderStyle)。這些對象在某些情況下,可以被節點共享。這些節點是兄弟節點以及:

          • 1、這些元素必須在相同的鼠標狀態下(比如,不能一個是 :hover 狀態,其他的不是)
          • 2、元素不應該有 ID
          • 3、標簽名稱應該能匹配
          • 4、class 屬性應該能匹配
          • 5、映射屬性集必須完全相同
          • 6、link 狀態必須匹配
          • 7、focus 狀態必須匹配
          • 8、元素不能受到屬性選擇器的影響,影響被定義為可匹配到使用了元素中的任何屬性的屬性選擇器。
          • 9、元素不能存在行內樣式屬性
          • 10、不能使用兄弟選擇器。WebCore 遇到兄弟選擇器時會拋出一個全局開關,為整個文檔關閉樣式共享。這些選擇器包括:+ 選擇器,:first-child 和 :last-child 等。

          2.5.3.2 Firefox 規則樹(Firefox rule tree)

          為了更簡單的樣式計算,Firefox 提供了兩種樹——規則樹和樣式上下文樹。Webkit 也有樣式對象,但是他們不是儲存在類似于樣式上下文樹的樹中,它只有 DOM 節點指向相應的樣式。

          Firefox 樣式上下文樹:

          樣式上下文包含了端值(end values)。值的計算是通過按正確的順序應用匹配規則,以及執行將它們從邏輯值轉換成具體值的操作來完成的。 比如,如果邏輯值是屏幕的百分百,它會被計算并轉換成絕對的單位。規則樹的想法相當得聰明。它允許在不同節點間共享這些值,避免重復計算。同時也節省了空間。

          所有匹配到的規則都會儲存在樹中,在一條路徑中,越下面的節點擁有越高的優先級。樣式上下文樹包含了找到的規則匹配的所有路徑。規則的儲存是惰性的。 樣式上下文樹不會一開始就為每個節點進行計算,而是當節點需要被計算時,將計算路徑添加到樹中。

          我們的想法是將樹路徑看成是詞典中的單詞。假設我們已經計算好了規則樹:

          假設,我們需要在內容樹中為其他元素匹配規則,找到的規則(按正確的順序)是:B - E - I。這個規則已經存在于樹中,因為已經計算過 A - B - E - I - L 的路徑。現在我們要做的工作就少了。

          2.5.3.2.1 分結構(Division into structs)

          樣式上下文被分成不同的結構。每個結構都包含了某種分類(如border或color)的樣式信息。 結構中的所有屬性(properties)或者是繼承的或者是非繼承的。繼承屬性除非在元素上有定義屬性,一般會繼承父級。 非繼承屬性(也叫重置屬性)如果未定義,一般會使用默認的。

          樣式上下文樹會幫忙將樹中的結構緩存起來(包括計算的端值)。如果下層節點沒有為結構提供定義,就用緩存中的上層節點的的結構。

          2.5.3.2.2 使用規則樹計算樣式上下文(Computing the style contexts using the rule tree)

          為某個元素計算樣式上下文時,首先會去計算規則樹中的路徑,或者使用已經存在的路徑。然后在路徑中應用規則填充新樣式上下文中的結構。 我們從路徑的底層節點——優先級最高的節點(通常是最具體的選擇器)開始,然后往上遍歷上下文樹,直到將結構填充完成。 如果在規則樹中沒有對該結構的規定,那么我們就可以大幅優化——我們在樹中往上找,找到某個節點能完整得規定并指向這個結構——這是最好的優化——因為這整個結構是完全共享的。 這樣節省了端值的計算和內存。如果我們只找到部分定義,那么繼續在樹中往上找,直到將整個結構填充完整。

          如果我們沒有找到對該結構的任何定義,假如整個結構是一個"繼承"類型,就將該結構指向上下文樹的父級結構,在這種情況下,我們也成功共享了結構。 如果是重置結構,那么將使用默認值。

          如果最具體的節點有添加值,那么我們需要進行額外的計算,將這些值轉成實際的值,然后將結果儲存在樹節點中,供子節點使用。

          假如一個元素有子節點或兄弟節點,它們指向相同的樹節點,那么整個樣式上下文(entire style context)可以在它們之間共享。

          來看一個例子,

          假設有這么一段 HTML 代碼:

          <html>
           <body>
           <div class="err" id="div1">
           <p>
           this is a <span class="big"> big error </span>
           this is also a
           <span class="big"> very big error</span> error
           </p>
           </div>
           <div class="err" id="div2">another error</div>
           </body>
          </html>
          

          以及規則如下:

          1. div {
           margin: 5px;
           color: black;
           }
          2. .err {
           color: red;
           }
          3. .big {
           margin-top: 3px;
           }
          4. div span {
           margin-bottom: 4px;
           }
          5. #div1 {
           color: blue;
           }
          6. #div2 {
           color: green;
           }
          

          簡單來說,我們需要填充兩種結構——顏色結構(color struct)和邊緣空白結構(margin struct)。顏色只包含一個成員,而邊緣包含了四個邊。 規則樹看起來如下(節點以節點名——即序號標記)和語法樹如下(節點名,指向規則節點):

          規則樹(右側)和上下文樹(左側):

          上下文樹

          假設我們現在解析這段 HTML 代碼,遇到第二個 <div> 標簽,我們需要為這個節點創建一個樣式上下文,然后填充它的樣式結構。 匹配規則時,發現這個 <div> 標簽匹配到的規則是 1、2、6,這就是說,在樹中已經存在當前元素可以使用的路徑,現在僅需要將規則 6(規則樹中的 F) 添加到另一個節點中。 然后創建一個樣式上下文,將它存到上下文樹中。新的樣式上下文將指向規則樹中的節點 F。可參考下圖。

          現在填充樣式結構,從 margin 結構開始,因為最后一個規則節點(F)沒有加到 margin 結構中, 我們可以在樹中繼續往上找,直到找到之前的節點插入時計算并儲存起來的結構,并使用它。 最后在節點 B 中找到——規定了 margin 規則的最上面的節點。

          因為對顏色結構有定義,所以不能使用緩存的結構。因為顏色有一個屬性,所以我們不需要在樹中填充其他屬性。我們將計算端值(end value)——將字符串轉成 RGB 等——然后在這個節點中儲存計算好的結構。

          解析第二個 <span> 元素的工作更加簡單。我們匹配規則,發現它指向規則 G,和之前的 <span> 一樣。子節點指向了同一個節點,所以可以共享整個樣式上下文,指向前一個 span 元素的上下文。

          對于包含繼承自父級規則的結構,是儲存在上下文樹中的(color 屬性實際上繼承的,但是 Firefox 把它當成重置屬性,并緩存在規則樹中)。

          比如,我們給 paragraph 增加了 fonts 規則:

          p { font-family: Verdana; font-size: 10px; font-weight: bold; } 
          

          上下文樹中 p 元素的子級(child) —— div 元素就可以共享父級的相同 font 結構。前提是 div 元素沒有規定 font 規則。

          在 Webkit 中沒有規則樹,所以匹配聲明會被遍歷4次。

          • 首先,重要的高優先級的規則。(這些規則優先應用,因為其他屬性會依賴于它們,如 display)
          • 其次,高優先級重要的規則。
          • 然后,普通優先級不重要的規則。
          • 最后,普通優先級重要的規則。

          這就是說,多次出現的屬性會根據正確的層級順序進行解析,明顯后者(上下文規則樹)勝出。

          總結一下,共享樣式對象(全部或部分結構)解決了問題 1 和 3。Firefox 的規則樹對于按正確順序應用屬性也很有幫助。

          2.5.3.3 操作規則以便輕松匹配(Manipulating the rules for an easy match)

          樣式規則的來源:

          • CSS 規則,外部或內部樣式表
          p { color: blue; } 
          
          • 行內樣式屬性
          <p style="color: blue;"></p>
          
          • HTML 視覺屬性(映射到對應的樣式規則)
          <p bgcolor="blue"></p>
          

          后面兩個很容易匹配到,因為它擁有樣式屬性和 HTML 屬性,可以將元素當做 key 來進行映射。

          正如之前提到過的問題 2,css 規則匹配更加棘手。 為了解決這個問題,規則被改成更加容易訪問。

          解析完樣式表后,根據選擇器,將訪問規則添加到其中的某個哈希映射中。 有ID映射,類名(class name) 映射,標簽名(tag name)映射,和一個普通映射包含了其他不屬于前面幾種分類的選擇器。 如果選擇器是 id,就將規則添加到 ID 映射表中,如果是類名,就將規則添加到類名映射表中,以此類推。

          這樣的操作使得規則匹配更加容易。這樣就沒必要在每個聲明中查找,能從映射表中提取出元素相對應的規則。

          這個優化排除了 95% 以上的規則,甚至在匹配過程中都不需要考慮(4.1)。

          來看下面的樣式規則:

          p.error {
           color: red;
          }
          #messageDiv {
           height:50px;
          }
          div {
           margin: 5px;
          }
          

          第一個規則將被插入到類名映射表中,第二個插入到ID映射表中,第三個插入到標簽名映射表中。

          繼續看下面的 HTML 片段:

          <p class="error">an error occurred </p>
          <div id=" messageDiv">this is a message</div>
          

          先嘗試為 p 元素找到規則,類名映射表中包含了名為 "error" 的 key,在該 key 下可以找到 "p.error" 的規則。 div 元素可以在 ID映射表(key 為 id)和標簽名映射表中找到相應的規則。

          所以現在唯一的工作就是,找出按 key 提取出的規則中哪些規則是真正匹配的。

          比如,div 的規則是這樣的:

          table div {
           margin: 5px;
          }
          

          現在仍是從 標簽映射表 中提取,但是 key 是最右邊的選擇器,但是它不會匹配上面的 div,因為它沒有 table 祖先。

          對于上面的問題,Webkit 和 Firefox 都做了相應的操作(manipulation)。

          2.5.3.4 按正確的層級順序應用規則(Applying the rules in the correct cascade order)

          樣式對象的屬性與每個視覺屬性相對應。如果某個屬性沒有被任何匹配的規則所定義,那么就可以從父級元素的樣式對象上繼承某些屬性。其他的屬性使用默認值。

          當有多個定義時,問題就出現了——層級順序就是為了解決這個問題的。

          2.5.3.4.1 樣式表層疊順序(Style sheet cascade order)

          一個樣式屬性的聲明可以在多個樣式表中出現,也可在同個樣式表中出現多次。這就意味著規則的應用順序非常重要。這叫作 "層級" 順序。 根據 CSS2 的規范,層級順序如下(從低到高):

          • 瀏覽器聲明(Browser declarations)
          • 用戶的標準聲明(User normal declarations)
          • 作者的標準聲明(Author normal declarations)
          • 作者的重要聲明(Author important declarations)
          • 用戶的重要聲明(User important declarations)

          瀏覽器聲明是最不重要的,用戶聲明僅在設為重要(important)時才會覆蓋作者的聲明。 順序相同的聲明會根據指定的順序來分類,然后按指定的順序來排序。 HTML 視覺屬性會被翻譯并匹配 CSS 的聲明。這些被認為是低優先級的用戶規則。

          2.5.3.4.2 指定順序(Specifity)

          選擇器順序指定由 CSS2 規范規定,具體如下:

          • 如果聲明是來自 "style" 屬性,而不是來自選擇器規則,就加1,否則就加0(=a)
          • 累加選擇器中 ID 屬性的數量(=b)
          • 累加選擇器中的其他屬性和偽類的數量(=c)
          • 累加選擇器中的元素名和偽元素的數量(=d)

          連接數字 a-b-c-d (在一個大型的數字系統中)算出指定順序。

          需要用到的基數由以上四個類型中數字最大的來決定。

          比如說,a = 14,那么可以使用十六進制。當 a = 17時,需要一個17位數的基數。 后者發生的情況是有一個類似于這樣的選擇器:html body div div p ... (選擇器中有17個標簽,雖然可能性不是很大)。

          一些示例:

          2.5.3.4.3 規則排序(Sorting the rules)

          樣式規則匹配完成后,會根據層級規則對其進行排序。 對于數據量小的列表,Webkit 使用冒泡排序(bubble sort),而對于數據量大的列表,使用混合排序(merge sort)。

          Webkit 通過重寫 “>” 操作符為下面的規則實現排序:

          static bool operator >(CSSRuleData& r1, CSSRuleData& r2)
          {
           int spec1 = r1.selector()->specificity();
           int spec2 = r2.selector()->specificity();
           return (spec1 == spec2) : r1.position() > r2.position() : spec1 > spec2; 
          }
          

          2.5.4 漸進的過程(Gradual process)

          Webkit 使用一個標志來標記頂層樣式表是否加載完成(包括 @imports)。 當使用樣式時,發現樣式沒有完全加載完成,將會使用占位符,并且在文檔中進行標記,當樣式加載完成時,會重新進行計算。

          2.6 布局(Layout)

          渲染器創建完成并被添加到渲染樹時,它沒有位置(position)和 大小(size)。

          計算這些值的過程叫作 布局(layout)和 回流(reflow)。

          HTML 使用基于流的布局模型,這意味著大多數時候能一次性計算出幾何結構。 流后面的元素不會影響流前面的元素的幾何結構。所以布局可以按從左到右、從上到下的順序進行。 但也有例外——比如 tables,就需要多次計算(3.5)

          坐標系統和根框架相關,使用上側和左側坐標。

          布局是一個遞歸的過程。從根渲染器開始,與 HTML 文檔的元素相對應。布局會在其中一些或所有框架層級中持續遞歸,為每個渲染器計算幾何信息。

          根渲染器的位置是 0,0,它的大小是視口大小——瀏覽器窗口的可視部分。

          所有的渲染器都有一個 layout(布局) 和 reflow(回流) 方法,每個渲染器都會調用那些需要布局的子渲染器的 layout 方法。

          2.6.1 臟值系統(Dirty bit system)

          為了避免為每個小的變動都進行一次完整的布局,瀏覽器使用了臟值系統。

          一個渲染器改變或添加了之后會標記自己及其子代為“dirty”——需要布局。

          有兩種標志——“dirty”和“children are dirty”。后者意味著雖然渲染器本身沒問題,但是它至少有一個需要布局的子代。

          2.6.2 全局和遞增布局(Global and incremental layout)

          布局能在整個渲染樹上觸發——這就是全局布局(global layout)。以下幾種情形會引起全局布局:

          • 1、會影響到所有渲染器的全局樣式改變,比如字體大小的改變。
          • 2、屏幕大小調整。

          布局可以是遞增的,只有臟的渲染器會被布局(這可能會導致某些不好的影響,會引起額外的布局)。

          渲染器臟了就會(異步地)觸發遞增布局(incremental layout)。比如,當網絡請求到新的內容,添加到 DOM 樹后,就會有新的渲染器被添加到渲染樹。

          遞增布局——只有臟的渲染器及其子代會被布局

          2.6.3 異步和同步布局(Asynchronous and Synchronous layout)

          遞增布局是異步完成的。Firefox 會為遞增布局進行“回流請求”(reflow commands)排隊,然后通過調度程序觸發這些請求的批量執行。Webkit 也有一個執行遞增布局的定時器——遍歷渲染樹,布局臟渲染器。

          請求樣式信息,如 “offsightHeight” 的腳本會觸發同步遞增布局。 全局布局通常都是同步觸發的。

          有時候初始布局后的回調也會觸發布局,因為一些屬性(比如滾動位置)發生改變。

          2.6.4 優化(Optimizations)

          如果布局的觸發是由于大小調整(resize)或者渲染器的位置(position)改變——而不是大小改變,渲染器的大小會從緩存中獲取,不會重新計算。

          在某些情況下,當只有子渲染樹改變時,布局不會從根節點開始。發生這種情況的情形有:局部改變,不會影響周圍——比如文本插入到文本域中。否則的話,每次按鍵都會觸發從根節點開始的布局。

          2.6.5 布局過程(The layout process)

          布局通常有下面幾種模式:

          1. 父渲染器決定自己的寬度
          2. 父渲染器遍歷子渲染器,然后
          3. 放置子渲染器(設置它的x和y)
          4. 如有需要,調用子渲染器的layout(布局)方法——它們是臟的或者我們在全局布局中,或者其他某些原因——這會計算子渲染器的高度。
          5. 父渲染器使用子渲染器的高度、外邊距、內邊距的累加高度來設置自己的高度——父渲染器的父渲染器也會使用這個高度。
          6. 設置臟值為false。

          Firefox 使用狀態(state)對象(nsHTMLReflowState)作為 layout 方法的參數(術語叫“reflow”)。其中包含了父渲染器的寬度。

          Firefox layout 方法的輸出是一個度量(metrics)對象(nsHTMLReflowMetrics)。它包含渲染器計算的高度。

          2.6.6 寬度計算(Width calculation)

          渲染器的寬度是使用容器塊的寬度——渲染器的樣式 “width” 屬性和 margins 和 borders 來計算的。

          比如下面 div 的寬度:

          <div style="width: 30%"></div>
          

          Webkit 的計算方式如下(RenderBox類,calcWidth方法):

          • 容器的寬度是容器可用寬度(availableWidth)和0的最大值。在上面這個例子中,可用寬度就是內容寬度(contentWidth),計算方式如下:
          clientWidth() - paddingLeft() - paddingRight()
          

          clientWidth 和 clientHeight 指的是一個對象的內部寬高,除去邊(border)和滾動條(scrollbar)。

          • 元素的寬就是樣式屬性“width”的值,這個值是一個相對值,通過計算父容器的寬度的百分比得到。
          • 然后添加橫向邊和內邊距(horizontal borders and paddings)。

          目前為止,這是“首選寬度”(preferred width)的計算,接下來計算最小寬度和最大寬度(minimum and maximum width)。如果首選寬度的值比最大寬度的值大,將使用最大寬度的值。如果首選寬度的值小于最小寬度的值(最小的不可分割的單位),將使用最小寬度的值。

          這些值會緩存起來,以防在寬度沒有改變的情況下需要重新布局。

          2.6.7 折行(Line Breaking)

          在一個渲染器在布局過程中發現需要折行,它會停下來,通知父渲染器它需要折行。父渲染器就會創建額外的渲染器,然后調用這些渲染器的layout方法。

          2.7 繪制(Painting)

          在繪制階段,會遍歷渲染器樹,調用渲染器的 paint 方法,在屏幕上排列內容。繪制使用 UI 基礎組件,可以查看 UI 章節了解更多。

          2.7.1 全局和遞增(Global and Incremental)

          和布局(layout)一樣,繪制也可以是全局的——整個樹繪制,或遞增繪制。在遞增繪制時,有些渲染器會以某種方式改變,但不會影響整個樹。改變了的渲染器會使自己在屏幕上的矩形位置無效。這會使得 OS 認為它是臟區域,然后生成一個 paint 事件。OS 巧妙地處理,然后將這些區域合并成一個。在 Chrome 中更加復雜,因為渲染器和主流程不在同一個進程中。Chrome 中某些程度上模仿了 OS。繪制過程(presentation)監聽這些事件,并將信息傳遞給渲染根節點。它會遍歷渲染樹,直到找到相關的渲染器。它會重新繪制自己(通常也會重新繪制它的子級)。

          2.7.2 繪制順序(The painting order)

          CSS2 規定了繪制程序的順序——http://www.w3.org/TR/CSS21/zindex.html。這實際上就是元素在層疊上下文(stacking context)中如何層疊的順序。

          這個順序影響了繪制,因為層疊是從后到前的順序繪制的。一個塊級渲染器的層疊順序如下:

          1. background color(背景顏色)
          2. background image(背景圖片)
          3. border(邊)
          4. children(子級)
          5. outline(輪廓)

          2.7.3 Firefox 排列列表(Firefox display list)

          Firefox 遍歷渲染樹,然后為繪制的矩形創建一個排列列表。它包含了與矩形相對應的按正確繪制順序(渲染器的 backgrounds,borders 等)排序的渲染器。

          這樣,渲染樹只需要遍歷一次,而不用遍歷很多次——繪制所有的背景、所有的圖片、所有的邊等。

          Firefox 對繪制過程做了優化:不添加看不見的元素,如完全位于不透明元素下面的元素。

          2.7.4 Webit 矩形存儲(Webkit rectangle storage)

          在重新繪制前,webkit 將老的矩形存儲為位圖,然后只繪制這些處于新老矩形之間的區域。

          2.8 動態改變(Dynamic changes)

          瀏覽器嘗試對變化采取最小可能的行為。所以元素顏色的變化只會導致元素的重繪。元素位置的變化會導致元素及其子代或者同級元素的布局和重繪。增加 DOM 節點會導致節點的布局和重繪。重要的改變,如 html 元素的 font size,會導致緩存無效,然后觸發整個樹的重新布局和重繪。

          2.9 渲染引擎線程(The rendering engine's threads)

          渲染引擎是單線程。幾乎所有的操作,除了網絡操作,都是單線程。在 Firefox 和 Safari 中,這是瀏覽器的主線程。 在 Chrome 中,渲染引擎是 tab 進程的主線程。

          網絡操作可以以多個并行線程的方式執行。并行連接的個數有限制(通常是2-6個,Firefox3 使用 6個)。

          2.9.1 事件循環(Event loop)

          瀏覽器的主線程是一個事件循環。它是使進程保持活躍的無限循環。它等待事件(比如 layout 和 repaint事件),然后處理它們。下面是 Firefox 主要事件循環的代碼:

          while (!mExiting)
           NS_ProcessNextEvent(thread);
          

          2.10 CSS2 視覺模型(CSS2 visual model)

          2.10.1 畫布(The canvas)

          根據 CSS2 規范,術語 canvas 就是 “渲染格式化的結構的空間”——瀏覽器繪制內容的地方。

          根據 http://www.w3.org/TR/CSS2/zindex.html,被包含在其他 canvas 中的 canvas 是透明的,否則,就給定一個瀏覽器定義好的顏色。

          2.10.2 CSS 盒模型(CSS Box model)

          CSS 盒模型描述了在文檔樹中為元素生成的矩形盒子,然后通過視覺格式模型對其布局。每個盒子有一個內容區域(比如 text,image等),以及可選的周邊區域,如內邊距、邊、外邊距。

          • CSS2 盒模型*

          每個節點生成 0-n 個這樣的盒子。

          每個元素都有一個 display 屬性,決定了如何生成它們的盒子的類型。

          比如:

          block -- 生成一個塊級盒子
          inline -- 生成一個或多個行內盒子
          none -- 不會生成盒子
          

          默認是 inline,但是瀏覽器樣式表可以設置其他默認值。比如,div 元素的默認值是 block.

          關于默認樣式表的例子,可以查看http://www.w3.org/TR/CSS2/sample.html。

          2.10.3 定位方案(Positioning scheme)

          有三種定位方案:

          1. Normal —— 對象根據在文檔中的位置排列——對象在渲染樹中的位置和在 DOM 樹中的位置是一樣的,并且根據盒子的類型和大小來布局。
          2. Float —— 對象顯示按正常流布局,然后盡可能地往左或右移動。
          3. Absolute —— 對象在渲染樹中的位置和 DOM 樹中的位置完全不一樣。

          定位方案由 position 屬性和 floate 屬性決定:

          • static 和 normal 是正常流定位
          • absolute 和 fixed 是絕對定位

          在 static 定位中,不定義任何位置,并使用默認定位。在其他方案下,作者定義位置——top、bottom、left、right。

          盒子布局的方法由下面這些條件決定:

          • 盒子類型(Box type)
          • 盒子大小(Box dimensions)
          • 位置方案(Position scheme)
          • 其他信息——比如圖片大小和屏幕大小

          2.10.4 盒子類型(Box types)

          塊級盒子:形成一個塊——在瀏覽器窗口中有自己的矩形區域。

          塊級盒子

          行內盒子:沒有自己的塊,包含在塊中。

          行內盒子

          塊級盒子按垂直方向排列,行內盒子按水平方向排列。

          塊級和行內盒子排版

          行內盒子被放置在行內或行盒子中,當盒子以 baseline 的方式——元素的底部和另一個元素的某個點對其——來對齊時,這些行的高度至少和最高的盒子一樣高,但是不能比它高。如果行的寬度不夠寬,行會被放到很多行中。在 paragraph 中經常出現。

          2.10.5 定位(Positioning)

          2.10.5.1 相對定位(Relative)

          相對定位——像往常一樣定位然后按要求的delta移動。

          相對定位

          2.10.5.2 浮動(Floats)

          一個浮動盒子會被移動到行的左邊或右邊。有趣的是其他盒子圍繞它流動。比如下面的 HTML:

          <p>
          <img style="float:right" src="images/image.gif" width="100" height="100">Lorem ipsum dolor sit amet, consectetuer...
          </p>
          

          浮動

          2.10.5.3 (絕對和固定定位)Absolute and fixed

          無論正常流如何,都會精確定義布局。元素不會出現在正常流中。元素的大小是相對于容器的。在 fixed 中,容器是視口。

          固定定位

          注意:即使文檔滾動了,固定盒子也不會移動!

          2.10.6 分層呈現(Layered representation)

          它由 z-index 屬性決定。代表了元素的第三個尺寸,它的 z 軸位置。

          盒子被分成層級(叫做層級上下文)。在層級中,后面的元素會先繪制,前面的元素會繪制在上面,更接近用戶。

          層級根據 z-index 屬性來排序。具有 z-index 屬性的盒子形成本身的層級。視口有外部層級。

          比如:

          結果如下:

          固定位置

          雖然紅色 div 位于綠色之前,并且之前已經在常規流中繪制過,但是z-index屬性更高,因此它在根框所持有的堆棧中更向前。

          感謝閱讀,歡迎關注,更多干貨

          Venture Beat報道,近日,馬遜推出了Alexa Presentation Language 工具,旨在讓開發人員更輕松地為Alexa設備定義視覺元素,其中包括亞馬遜的Echo Show,Fire TV,Fire Tablet和Echo Spot等屏幕。APL 基于JSON的HTML5語言,由五個核心元素組成:圖像,文字和列表、布局,樣式和條件表達式、語音同步、幻燈片、按順序內置意圖。(品玩)


          主站蜘蛛池模板: 天堂一区二区三区在线观看| 中文字幕日韩人妻不卡一区| 亚洲AV色香蕉一区二区| 精品日本一区二区三区在线观看| 精品黑人一区二区三区| 亚洲AV无码一区二三区| 人妻内射一区二区在线视频| 亚洲一区AV无码少妇电影| 无码人妻精品一区二区在线视频 | 亚洲视频在线一区二区| 激情内射亚洲一区二区三区爱妻| 国产一区在线视频| 亚洲视频在线一区二区| 国产亚洲综合一区二区三区| 精品一区二区三区四区在线播放 | 色婷婷av一区二区三区仙踪林| 久久精品国产一区| 变态调教一区二区三区| 亚无码乱人伦一区二区| 亚洲男女一区二区三区| 久久无码人妻一区二区三区 | 国产成人精品无人区一区| 在线精品亚洲一区二区| 3d动漫精品啪啪一区二区中| 日本免费一区二区三区四区五六区| 亚洲一区精品伊人久久伊人| 亚洲一区精品伊人久久伊人| 中文字幕国产一区| 中文字幕一区二区三区乱码| 久久成人国产精品一区二区 | 一区二区三区杨幂在线观看| 国产午夜精品一区二区三区极品| 人妻视频一区二区三区免费| 无码人妻aⅴ一区二区三区| 国产日韩精品一区二区三区 | 亚洲一区精品伊人久久伊人| 国产无码一区二区在线| 日韩电影一区二区| 亚洲一区二区三区四区视频| 国内精品一区二区三区东京 | 国产一区二区三区免费观在线 |