摘要:也有的元素被完全無視,比如的元素。對于每個元素,必須在所有中找到符合的并將對應(yīng)的規(guī)則進(jìn)行合并。這樣做是為了減少無效匹配次數(shù),從而匹配快性能更優(yōu)。識別屬性值,創(chuàng)建對象,并將對象存入解釋器堆棧。數(shù)組中的每一項(xiàng)紀(jì)錄了這個的,的值,權(quán)重層疊規(guī)則。
作為前端,我們每天都在與CSS打交道,那么CSS的原理是什么呢?一、瀏覽器渲染
開篇,我們還是不厭其煩的回顧一下瀏覽器的渲染過程,先上圖:
正如上圖所展示的,我們?yōu)g覽器渲染過程分為了兩條主線:
其一,HTML Parser 生成的 DOM 樹;
其二,CSS Parser 生成的 Style Rules ;
在這之后,DOM 樹與 Style Rules 會生成一個新的對象,也就是我們常說的 Render Tree 渲染樹,結(jié)合 Layout 繪制在屏幕上,從而展現(xiàn)出來。
本文的重點(diǎn)也就集中在第二條分支上,我們來探究一下 CSS 解析原理。二、Webkit CSS 解析器
瀏覽器 CSS 模塊負(fù)責(zé) CSS 腳本解析,并為每個 Element 計算出樣式。CSS 模塊雖小,但是計算量大,設(shè)計不好往往成為瀏覽器性能的瓶頸。
CSS 模塊在實(shí)現(xiàn)上有幾個特點(diǎn):CSS 對象眾多(顆粒小而多),計算頻繁(為每個 Element 計算樣式)。這些特性決定了 webkit 在實(shí)現(xiàn) CSS 引擎上采取的設(shè)計,算法。如何高效的計算樣式是瀏覽器內(nèi)核的重點(diǎn)也是難點(diǎn)。
先來看一張圖:
Webkit 使用 Flex 和 Bison 解析生成器從 CSS 語法文件中自動生成解析器。
它們都是將每個 CSS 文件解析為樣式表對象,每個對象包含 CSS 規(guī)則,CSS 規(guī)則對象包含選擇器和聲明對象,以及其他一些符合 CSS 語法的對象,下圖可能會比較明了:
Webkit 使用了自動代碼生成工具生成了相應(yīng)的代碼,也就是說詞法分析和語法分析這部分代碼是自動生成的,而 Webkit 中實(shí)現(xiàn)的 CallBack 函數(shù)就是在 CSSParser 中。
CSS 的一些解析功能的入口也在此處,它們會調(diào)用 lex , parse 等生成代碼。相對的,生成代碼中需要的 CallBack 也需要在這里實(shí)現(xiàn)。
舉例來說,現(xiàn)在我們來看其中一個回調(diào)函數(shù)的實(shí)現(xiàn),createStyleRule(),該函數(shù)將在一般性的規(guī)則需要被建立的時候調(diào)用,代碼如下:
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; }
從該函數(shù)的實(shí)現(xiàn)可以很清楚的看到,解析器達(dá)到某條件需要創(chuàng)建一個 CSSStyleRule 的時候?qū)⒄{(diào)用該函數(shù),該函數(shù)的功能是創(chuàng)建一個 CSSStyleRule ,并將其添加已解析的樣式對象列表 m_parsedStyleObjects 中去,這里的對象就是指的 Rule 。
那么如此一來,經(jīng)過這樣一番解析后,作為輸入的樣式表中的所有 Style Rule 將被轉(zhuǎn)化為 Webkit 的內(nèi)部模型對象 CSSStyleRule 對象,存儲在 m_parsedStyleObjects 中,它是一個 Vector。
但是我們解析所要的結(jié)果是什么?
1.通過調(diào)用 CSSStyleSheet 的 parseString 函數(shù),將上述 CSS 解析過程啟動,解析完一遍后,把 Rule 都存儲在對應(yīng)的 CSSStyleSheet 對象中;
2.由于目前規(guī)則依然是不易于處理的,還需要將之轉(zhuǎn)換成 CSSRuleSet。也就是將所有的純樣式規(guī)則存儲在對應(yīng)的集合當(dāng)中,這種集合的抽象就是 CSSRuleSet;
3.CSSRuleSet 提供了一個 addRulesFromSheet 方法,能將 CSSStyleSheet 中的 rule 轉(zhuǎn)換為 CSSRuleSet 中的 rule ;
4.基于這些個 CSSRuleSet 來決定每個頁面中的元素的樣式;
三、CSS 選擇器解析順序可能很多同學(xué)都知道排版引擎解析 CSS 選擇器時是從右往左解析,這是為什么呢?
1.HTML 經(jīng)過解析生成 DOM Tree(這個我們比較熟悉);而在 CSS 解析完畢后,需要將解析的結(jié)果與 DOM Tree 的內(nèi)容一起進(jìn)行分析建立一棵 Render Tree,最終用來進(jìn)行繪圖。Render Tree 中的元素(WebKit 中稱為「renderers」,F(xiàn)irefox 下為「frames」)與 DOM 元素相對應(yīng),但非一一對應(yīng):一個 DOM 元素可能會對應(yīng)多個 renderer,如文本折行后,不同的「行」會成為 render tree 種不同的 renderer。也有的 DOM 元素被 Render Tree 完全無視,比如 display:none 的元素。
2.在建立 Render Tree 時(WebKit 中的「Attachment」過程),瀏覽器就要為每個 DOM Tree 中的元素根據(jù) CSS 的解析結(jié)果(Style Rules)來確定生成怎樣的 renderer。對于每個 DOM 元素,必須在所有 Style Rules 中找到符合的 selector 并將對應(yīng)的規(guī)則進(jìn)行合并。選擇器的「解析」實(shí)際是在這里執(zhí)行的,在遍歷 DOM Tree 時,從 Style Rules 中去尋找對應(yīng)的 selector。
3.因?yàn)樗袠邮揭?guī)則可能數(shù)量很大,而且絕大多數(shù)不會匹配到當(dāng)前的 DOM 元素(因?yàn)閿?shù)量很大所以一般會建立規(guī)則索引樹),所以有一個快速的方法來判斷「這個 selector 不匹配當(dāng)前元素」就是極其重要的。
4.如果正向解析,例如「div div p em」,我們首先就要檢查當(dāng)前元素到 html 的整條路徑,找到最上層的 div,再往下找,如果遇到不匹配就必須回到最上層那個 div,往下再去匹配選擇器中的第一個 div,回溯若干次才能確定匹配與否,效率很低。
對于上述描述,我們先有個大概的認(rèn)知。接下來我們來看這樣一個例子,參考地址:
span> 111 span>
span> 222 span>
333
444
CSS 選擇器:
div > div.jartto p span.yellow{ color:yellow; }對于上述例子,如果按從左到右的方式進(jìn)行查找:
1.先找到所有 div 節(jié)點(diǎn);
2.在 div 節(jié)點(diǎn)內(nèi)找到所有的子 div ,并且是 class = “jartto”;
3.然后再依次匹配 p span.yellow 等情況;
4.遇到不匹配的情況,就必須回溯到一開始搜索的 div 或者 p 節(jié)點(diǎn),然后去搜索下個節(jié)點(diǎn),重復(fù)這樣的過程。這樣的搜索過程對于一個只是匹配很少節(jié)點(diǎn)的選擇器來說,效率是極低的,因?yàn)槲覀兓ㄙM(fèi)了大量的時間在回溯匹配不符合規(guī)則的節(jié)點(diǎn)。如果換個思路,我們一開始過濾出跟目標(biāo)節(jié)點(diǎn)最符合的集合出來,再在這個集合進(jìn)行搜索,大大降低了搜索空間。來看看從右到左來解析選擇器:
1.首先就查找到 的元素;
2.緊接著我們判斷這些節(jié)點(diǎn)中的前兄弟節(jié)點(diǎn)是否符合 P 這個規(guī)則,這樣就又減少了集合的元素,只有符合當(dāng)前的子規(guī)則才會匹配再上一條子規(guī)則。結(jié)果顯而易見了,眾所周知,在 DOM 樹中一個元素可能有若干子元素,如果每一個都去判斷一下顯然性能太差。而一個子元素只有一個父元素,所以找起來非常方便。試想一下,如果采用從左至右的方式讀取 CSS 規(guī)則,那么大多數(shù)規(guī)則讀到最后(最右)才會發(fā)現(xiàn)是不匹配的,這樣會做費(fèi)時耗能,最后有很多都是無用的;而如果采取從右向左的方式,那么只要發(fā)現(xiàn)最右邊選擇器不匹配,就可以直接舍棄了,避免了許多無效匹配。
瀏覽器 CSS 匹配核心算法的規(guī)則是以從右向左方式匹配節(jié)點(diǎn)的。這樣做是為了減少無效匹配次數(shù),從而匹配快、性能更優(yōu)。四、CSS 語法解析過程CSS 樣式表解析過程中講解的很細(xì)致,這里我們只看 CSS 語法解釋器,大致過程如下:
五、內(nèi)聯(lián)樣式如何解析?
1.先創(chuàng)建 CSSStyleSheet 對象。將 CSSStyleSheet 對象的指針存儲到 CSSParser 對象中。
2.CSSParser 識別出一個 simple-selector ,形如 “div” 或者 “.class”。創(chuàng)建一個 CSSParserSelector 對象。
3.CSSParser 識別出一個關(guān)系符和另一個 simple-selecotr ,那么修改之前創(chuàng)建的 simple-selecotr, 創(chuàng)建組合關(guān)系符。
4.循環(huán)第3步直至碰到逗號或者左大括號。
5.如果碰到逗號,那么取出 CSSParser 的 reuse vector,然后將堆棧尾部的 CSSParserSelector 對象彈出存入 Vecotr 中,最后跳轉(zhuǎn)至第2步。如果碰到左大括號,那么跳轉(zhuǎn)至第6步。
6.識別屬性名稱,將屬性名稱的 hash 值壓入解釋器堆棧。
7.識別屬性值,創(chuàng)建 CSSParserValue 對象,并將 CSSParserValue 對象存入解釋器堆棧。
8.將屬性名稱和屬性值彈出棧,創(chuàng)建 CSSProperty 對象。并將 CSSProperty 對象存入 CSSParser 成員變量m_parsedProperties 中。
9.如果識別處屬性名稱,那么轉(zhuǎn)至第6步。如果識別右大括號,那么轉(zhuǎn)至第10步。
10.將 reuse vector 從堆棧中彈出,并創(chuàng)建 CSSStyleRule 對象。CSSStyleRule 對象的選擇符就是 reuse vector, 樣式值就是 CSSParser 的成員變量 m_parsedProperties 。
11.把 CSSStyleRule 添加到 CSSStyleSheet 中。
12.清空 CSSParser 內(nèi)部緩存結(jié)果。
13.如果沒有內(nèi)容了,那么結(jié)束。否則跳轉(zhuǎn)值第2步。通過上文的了解,我們知道,當(dāng) CSS Parser 解析完 CSS 腳本后,會生成 CSSStyleSheetList ,他保存在Document 對象上。為了更快的計算樣式,必須對這些 CSSStyleSheetList 進(jìn)行重新組織。
計算樣式就是從 CSSStyleSheetList 中找出所有匹配相應(yīng)元素的 property-value 對。匹配會通過CSSSelector 來驗(yàn)證,同時需要滿足層疊規(guī)則。
將所有的 declaration 中的 property 組織成一個大的數(shù)組。數(shù)組中的每一項(xiàng)紀(jì)錄了這個 property 的selector,property 的值,權(quán)重(層疊規(guī)則)。
可能類似如下的表現(xiàn):
p > a { color : red; background-color:black; } a { color : yellow } div { margin : 1px; }重新組織之后的數(shù)組數(shù)據(jù)為(weight我只是表示了他們之間的相對大小,并非實(shí)際值。)
selector selector weight a color:yellow 1 p > a color:red 2 p > a background-color:black 2 div margin:1px 3 好了,到這里,我們來解決上述問題:
首先,要明確,內(nèi)斂樣式只是 CSS 三種加載方式之一;
其次,瀏覽器解析分為兩個分支,HTML Parser 和 CSS Parser,兩個 Parser 各司其職,各盡其責(zé);
最后,不同的 CSS 加載方式產(chǎn)生的 Style rule ,通過權(quán)重來確定誰覆蓋誰;到這里就不難理解了,對瀏覽器來說,內(nèi)聯(lián)樣式與其他的加載樣式方式唯一的區(qū)別就是權(quán)重不同。深入了解,請閱讀Webkit CSS引擎分析
六、何謂 computedStyle ?到這里,你以為完了?Too young too simple, sometimes naive!
瀏覽器還有一個非常棒的策略,在特定情況下,瀏覽器會共享 computedStyle,網(wǎng)頁中能共享的標(biāo)簽非常多,所以能極大的提升執(zhí)行效率!如果能共享,那就不需要執(zhí)行匹配算法了,執(zhí)行效率自然非常高。
也就是說:如果兩個或多個 element 的 computedStyle 不通過計算可以確認(rèn)他們相等,那么這些 computedStyle 相等的 elements 只會計算一次樣式,其余的僅僅共享該 computedStyle 。
那么有哪些規(guī)則會共享 computedStyle 呢?
該共享的element不能有id屬性且CSS中還有該id的StyleRule.哪怕該StyleRule與Element不匹配。
tagName和class屬性必須一樣;
mappedAttribute必須相等;
不能使用sibling selector,譬如:first-child, :last-selector, + selector;
不能有style屬性。哪怕style屬性相等,他們也不共享;
span>p style="color:red">paragraph1span>p> span>p style="color:red">paragraph2span>p>當(dāng)然,知道了共享 computedStyle 的規(guī)則,那么反面我們也就了解了:不會共享 computedStyle 的規(guī)則,這里就不展開討論了。
深入了解,請參考:Webkit CSS 引擎分析 - 高效執(zhí)行的 CSS 腳本
七、眼見為實(shí)
如上圖,我們可以看到不同的 CSS 選擇器的組合,解析速度也會受到不同的影響,你還會輕視 CSS 解析原理嗎?感興趣的同學(xué)可以參考這里:speed/validity selectors test for frameworks
八、有何收獲?1.使用 id selector 非常的高效。在使用 id selector 的時候需要注意一點(diǎn):因?yàn)?id 是唯一的,所以不需要既指定 id 又指定 tagName:
Bad p#id1 {color:red;} Good #id1 {color:red;}當(dāng)然,你非要這么寫也沒有什么問題,但這會增加 CSS 編譯與解析時間,實(shí)在是不值當(dāng)。2.避免深層次的 node ,譬如:
Bad div > div > div > p {color:red;} Good p-class{color:red;}3.慎用 ChildSelector ;
4.不到萬不得已,不要使用 attribute selector,如:p[att1=”val1”]。這樣的匹配非常慢。更不要這樣寫:p[id=”id1”]。這樣將 id selector 退化成 attribute selector。
Bad p[id="id1"]{color:red;} p[class="class1"]{color:red;} Good #id1{color:red;} .class1{color:red;}5.理解依賴?yán)^承,如果某些屬性可以繼承,那么自然沒有必要在寫一遍;
九、總結(jié)
6.規(guī)范真的很重要,不僅僅是可讀性,也許會影響你的頁面性能。這里推薦一個 CSS 規(guī)范,可以參考一下。“學(xué)會使用”永遠(yuǎn)都是最基本的標(biāo)準(zhǔn),但是懂得原理,你才能觸類旁通,超越自我。文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/117294.html
相關(guān)文章
天天都在使用CSS,那么CSS的原理是什么呢?
摘要:也有的元素被完全無視,比如的元素。對于每個元素,必須在所有中找到符合的并將對應(yīng)的規(guī)則進(jìn)行合并。這樣做是為了減少無效匹配次數(shù),從而匹配快性能更優(yōu)。識別屬性值,創(chuàng)建對象,并將對象存入解釋器堆棧。數(shù)組中的每一項(xiàng)紀(jì)錄了這個的,的值,權(quán)重層疊規(guī)則。 作為前端,我們每天都在與CSS打交道,那么CSS的原理是什么呢? 一、瀏覽器渲染 開篇,我們還是不厭其煩的回顧一下瀏覽器的渲染過程,先上圖:show...
前端知識點(diǎn)整理
摘要:難怪超過三分之一的開發(fā)人員工作需要一些知識。但是隨著行業(yè)的飽和,初中級前端就業(yè)形勢不容樂觀。整個系列的文章大概有篇左右,從我是如何成為一個前端工程師,到各種前端框架的知識。 為什么 call 比 apply 快? 這是一個非常有意思的問題。 作者會在參數(shù)為3個(包含3)以內(nèi)時,優(yōu)先使用 call 方法進(jìn)行事件的處理。而當(dāng)參數(shù)過多(多余3個)時,才考慮使用 apply 方法。 這個的原因...
前端知識點(diǎn)整理
摘要:難怪超過三分之一的開發(fā)人員工作需要一些知識。但是隨著行業(yè)的飽和,初中級前端就業(yè)形勢不容樂觀。整個系列的文章大概有篇左右,從我是如何成為一個前端工程師,到各種前端框架的知識。 為什么 call 比 apply 快? 這是一個非常有意思的問題。 作者會在參數(shù)為3個(包含3)以內(nèi)時,優(yōu)先使用 call 方法進(jìn)行事件的處理。而當(dāng)參數(shù)過多(多余3個)時,才考慮使用 apply 方法。 這個的原因...
如何學(xué)JavaScript
摘要:書籍如下面向?qū)ο缶幊讨改希L(fēng)格輕松易懂,比較適合初學(xué)者,原型那塊兒講得透徹,種繼承方式呢。還有另一件事情是,比如發(fā)現(xiàn)自己某個知識點(diǎn)不太清楚,可以單獨(dú)去百度。 作者:小不了鏈接:https://zhuanlan.zhihu.com/p/...來源:知乎著作權(quán)歸作者所有。商業(yè)轉(zhuǎn)載請聯(lián)系作者獲得授權(quán),非商業(yè)轉(zhuǎn)載請注明出處。 鑒于時不時,有同學(xué)私信問我(老姚,下同)怎么學(xué)前端的問題。這里統(tǒng)一回...
margin合并、塌陷,清除浮動
摘要:合并的外邊距的高度等于兩個發(fā)生合并的外邊距的高度中的較大者。那我們怎么辦,回到主題清除浮動。你可能忘了偽元素是行內(nèi)元素,只有塊元素才能清除浮動。所以我們最好后,主動清除一下浮動,避免以后遇到很奇怪的問題。 這是我的第一篇掘金文章,希望大家不要嫌棄。我還是一名在校大學(xué)生,就是想把自己所學(xué)到的知識寫出來,加深自己的印象,記錄自己成長的過程,這篇文章主要是介紹HTML 、 CSS 的一些小知...
發(fā)表評論
0條評論
lijy91
男|高級講師
TA的文章
閱讀更多
Dmit,全新日本Pro VPS 8折,100M大帶寬,CN2 GIA/10099(9929)/CM
閱讀 3110·2021-11-10 11:36
實(shí)戰(zhàn)!《長津湖》為什么這么火爆?我用 Python 來分析貓眼影評
閱讀 3312·2021-10-13 09:40
Arduino開發(fā)-TFT_eSPI庫學(xué)習(xí)
閱讀 6050·2021-09-26 09:46
天天都在使用CSS,那么CSS的原理是什么呢?
閱讀 662·2019-08-30 15:55
Codepen 每日精選(2018-4-10)
閱讀 1409·2019-08-30 15:53
CSS網(wǎng)格布局基礎(chǔ)
閱讀 1579·2019-08-29 13:55
純CSS的星級評價效果
閱讀 2997·2019-08-29 12:46
CSS題目及答案整理
閱讀 3204·2019-08-29 12:34