「三」瀏覽器中CSS 語法解析過程

CSS 語法解析過程

1.在瀏覽器系列文章中,今天終點講下CSS解析這塊內容.我們已知瀏覽器的渲染流程中HTML Parser會生成 DOM樹,而 CSS Parser會將解析結果附加到 DOM 樹上,如下圖:

解析分爲詞法分析 和 語法分析。

詞法分析,也是編譯原理中的術語,從左到右一個字符一個字符的讀入源程序,對字符流進行掃描,根據構詞規則識別單詞。這一過程可以使用lex等工具自動生成。

語法分析,主要任務是在詞法分析的基礎上,將單詞序列組合成各類語法短語,如“程序”, “語句”,“表達式”

解析工作通常會被拆分爲兩個組件:
  1. 詞法分析器,負責將輸入流分解成有效的字符。
  2. 解析器,負責根據不同語言的語法規則來分析文檔結構,最後構造出解析樹。
    詞法分析器知道如何去除不相關的字符,比如空格和換行

具體到css解析,因爲它是上下文無關的語法,可以利用各種解析器進行解析。webkit 使用Flex 和 Bison 解析器生成器,通過css 語法文件自動創建解析器。解析器將CSS文件解析成StyleSheet對象,且每個對象都包含CSS規則。CSS規則包含選擇器和聲明對象。

2.CSS 有自己的規則,一般如下:
WebKit 使用 FlexBison 解析器生成器,通過 CSS 語法文件自動創建解析器。Bison 會創建自下而上的移位歸約解析器。Firefox 使用的是人工編寫的自上而下的解析器。

這兩種解析器都會將 CSS 文件解析成 StyleSheet 對象,且每個對象都包含 CSS 規則。CSS 規則對象則包含選擇器和聲明對象,以及其他與 CSS 語法對應的對象。

3.CSS 解析過程會按照 RuleDeclaration 來操作:

4.那麼他是如何解析的呢,我們不妨打印一下 CSS Rules

控制檯輸入:

document.styleSheets[0].cssRules

打印出來的結果大致分爲幾類:

  • cssText:存儲當前節點規則字符串
  • parentRule:父節點的規則
  • parentStyleSheet:包含 cssRules,ownerNode,rules 規則

規則貌似有點看不懂,不用着急,我們接着往下看。

打印出來的結果大致分爲幾類:

5.CSS 解析和 Webkit 有什麼關係?

CSS 依賴 WebCore 來解析,而 WebCore 又是 Webkit 非常重要的一個模塊。

要了解 WebCore 是如何解析的,我們需要查看相關源碼

CSSRule* CSSParser::createStyleRule(CSSSelector* selector)  
{  
    CSSStyleRule* rule = 0;  
    if (selector) {  
        rule = new CSSStyleRule(styleElement);  
        m_parsedStyleObjects.append(rule);  
        rule->setSelector(sinkFloatingSelector(selector));  
        rule->setDeclaration(new CSSMutableStyleDeclaration(rule, parsedProperties, numParsedProperties));  
    }  
    clearProperties();  
    return rule;  
}

從該函數的實現可以很清楚的看到,解析器達到某條件需要創建一個 CSSStyleRule 的時候將調用該函數,該函數的功能是創建一個 CSSStyleRule,並將其添加已解析的樣式對象列表 m_parsedStyleObjects 中去,這裏的對象就是指的 Rule

注意:源碼是爲了參考理解,不需要逐行閱讀!

Webkit 使用了自動代碼生成工具生成了相應的代碼,也就是說詞法分析和語法分析這部分代碼是自動生成的,而 Webkit 中實現的 CallBack 函數就是在 CSSParser 中。

CSS 選擇器執行順序

渲染引擎解析 CSS 選擇器時是從右往左解析,這是爲什麼呢?舉個例子:

<div>
   <div class="jartto">
      <p><span> 111 </span></p>
      <p><span> 222 </span></p>
      <p><span> 333 </span></p>
      <p><span class='yellow'> 444 </span></p>
   </div>
</div>

<style>
  div > div.jartto p span.yellow {
   color: yellow;
  }
</style>

我們按照「從左到右」的方式進行分析:

  1. 先找到所有 div 節點。
  2. div 節點內找到所有的子 div,並且是 class = “jartto”
  3. 然後再依次匹配 p span.yellow 等情況。
  4. 遇到不匹配的情況,就必須回溯到一開始搜索的 div 或者 p 節點,然後去搜索下個節點,重複這樣的過程。

這樣的搜索過程對於一個只是匹配很少節點的選擇器來說,效率是極低的,因爲我們花費了大量的時間在回溯匹配不符合規則的節點。

我們按照「從右向左」的方式進行分析:

  1. 首先就查找到 class=“yellow”span 元素。
  2. 接着檢測父節點是否爲 p 元素,如果不是則進入同級其他節點的遍歷,如果是則繼續匹配父節點滿足 class=“jartto”div 容器。
  3. 這樣就又減少了集合的元素,只有符合當前的子規則纔會匹配再上一條子規則。

綜上所述,我們可以得出結論:

瀏覽器 CSS 匹配核心算法的規則是以從右向左方式匹配節點的。

這樣做是爲了減少無效匹配次數,從而匹配快、性能更優。

所以,我們在書寫 CSS Selector 時,從右向左的 Selector Term 匹配節點越少越好。

不同 CSS 解析器對 CSS Rules 解析速度差異也很大,感興趣的童鞋可以看看 CSS 解析引擎,這裏不再贅述。

參考文檔:
https://segmentfault.com/a/1190000018759693
https://segmentfault.com/a/1190000021073560

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章