CSS元素選擇器是怎樣運(yùn)作的?
在前端工程師的日常工作中,使用 CSS 元素選擇器是稀松平常的事;無(wú)論你是編寫一般的 CSS 還是需要經(jīng)過(guò)編譯的 SASS,SCSS,LESS等,最終都被編譯成一行一行的 CSS 樣式屬性,最終交給瀏覽器解析并套用。但是你想過(guò)沒(méi)有這是如何實(shí)現(xiàn)的呢?
瀏覽器渲染
我們先看一下瀏覽器的渲染步驟:
CSS 在被瀏覽器加載后,會(huì)被解析成 CSSOM 樹(shù),并嘗試與 Dom 疊加成渲染樹(shù),隨后進(jìn)行計(jì)算位置、渲染等步驟。這樣看來(lái),CSS 屬性套用的關(guān)鍵就在于如何從 CSS 轉(zhuǎn)化成 CSSOM 樹(shù),以及怎么把 CSSOM 套用到 DOM 上去。
CSSOM樹(shù)
當(dāng)我們寫下一組 CSS 樣式時(shí),例如:
- #id .class h4 + p {
 - ...
 - }
 
瀏覽器在解析它時(shí),你可能會(huì)認(rèn)為 CSS 會(huì)按照由左到右的依序找出#id>.class>h4>p,最后套用,但實(shí)際上瀏覽器解析 CSS 的順序是由右到左的 p>h4>.class>#id。
很違背直覺(jué)對(duì)吧?但如果考慮到性能問(wèn)題,從右到左的解析會(huì)比從左到右強(qiáng)很多。
假設(shè)這有這樣的 HTML:
- <div id="div1">
 - <div class="a">
 - <div class="b">
 - ...
 - </div>
 - <div class="c">
 - <div class="d">
 - ...
 - </div>
 - <div class="e">
 - ...
 - </div>
 - </div>
 - </div>
 - <div class="f">
 - <div class="c">
 - <div class="d">
 - ...
 - </div>
 - </div>
 - </div>
 - </div>
 
以及這邊五條 CSS 樣式規(guī)則:
- #div1 .c .d {}
 - .f .c .d {}
 - .a .c .e {}
 - #div1 .f {}
 - .c .d {}
 
讓我們模擬一下,如果把 CSS 從左到右解析,將會(huì)生成類似這樣的 CSSOM 樹(shù):
通過(guò)<div class =“ d”>中的 .d 來(lái)思考,這樣的 CSSOM 樹(shù)在套用樣式時(shí),必須對(duì)所有的樣式規(guī)則進(jìn)行檢查,以確認(rèn)樣式規(guī)則是否會(huì)影響到 .d,到最后才能確定可能會(huì)影響到 .d 的樣式規(guī)則有這三條:
- #div1 .c .d
 - .f .c .d
 - .c .d
 
以此類推,每個(gè) DOM 樹(shù)上的元素,都必須便利所有的樣式規(guī)則,才可以取得個(gè)別的樣式,這樣會(huì)造成大量冗余的計(jì)算,進(jìn)而嚴(yán)重影響性能。
反過(guò)來(lái),如果將前面的 CSS 由右到左進(jìn)行解析,CSSOM 樹(shù)則可能會(huì)如下:
和前面的例子一樣,從<div class =“ d”>中 .d 的角度來(lái)看,由于會(huì)被樣式規(guī)則影響到的目標(biāo)元素,已經(jīng)全都集中在第一層了,所以就不用再去便利整個(gè) CSSOM 樹(shù)了,甚至只需要檢查 .d 以下的子屬性變量是否符合實(shí)際 DOM 結(jié)構(gòu),再將所有符合的樣式規(guī)則重新取回,便能完成 .d 對(duì)元素的樣式規(guī)則套用。
從右到左的解析順序能夠?qū)⑺泄蚕淼囊?guī)則路徑收攏在一起,當(dāng)瀏覽器進(jìn)行屬性比對(duì)時(shí),就不用再便利整個(gè) CSSOM 樹(shù),大大的減少了無(wú)效的比對(duì)計(jì)算。
也可以換個(gè)方式思考:在 HTML 的結(jié)構(gòu)中,一個(gè)元素可以有無(wú)數(shù)個(gè)子元素,但只能有一個(gè)父元素,由子找父(由下往上)搜尋絕對(duì)是比較快的。
1. 套用樣式
將 CSSOM 樹(shù)解析出來(lái)之后就能夠和 DOM 結(jié)合了嗎?如果真的有這么簡(jiǎn)單就太好了。
除了開(kāi)發(fā)者定義好的 CSS 檔外,還有幾個(gè)地方可能會(huì)定義樣式規(guī)則,影響畫面的渲染:
- HTML 的 inline style 設(shè)置
 - 瀏覽器預(yù)設(shè)值(就是 CSS reset/normalize 要覆蓋掉的東西)
 - 瀏覽器的使用者偏好設(shè)定
 
瀏覽器負(fù)責(zé)處理 CSS 的部分,會(huì)吧前面所有的東西以及 CSS 文件定義的樣式規(guī)則分別整理成單獨(dú)的樣式規(guī)則組(CSS 規(guī)則集),內(nèi)容記載了樣式規(guī)則、目標(biāo)屬性等信息。
2. 目標(biāo)屬性
為了提升后面的計(jì)算效率,瀏覽器的 CSS 處理內(nèi)核會(huì)按照樣式規(guī)則組中個(gè)別規(guī)則的目標(biāo)屬性將其分組存放;一共分為以下四組
- idRules
 - classRules
 - tagNameRules
 - universalRules
 
這樣在取用時(shí),可以依據(jù)目標(biāo)元素是否存在這個(gè)屬性,快速篩出可能會(huì)套用的樣式。
套用規(guī)則
最后是套用規(guī)則。瀏覽器會(huì)遵循以下順序和樣式規(guī)則權(quán)重套用所有的樣式規(guī)則:
- 瀏覽器的預(yù)設(shè)值
 - 瀏覽器的使用者偏好設(shè)定
 - 開(kāi)發(fā)者定義的 CSS
 - inline style
 - 加上 !important 的樣式屬性
 
你可能會(huì)好奇:為什么 inline style 和開(kāi)發(fā)者定義的 CSS 會(huì)被另外處理?
我們可以回顧一下瀏覽器渲染的步驟,由于 inline style 存在于 DOM 元素中,只能在 CSS 套用到 DOM 上時(shí)才會(huì)接觸到,事前無(wú)法將兩者結(jié)合。
CSS 效率
實(shí)際上瀏覽器在這里已經(jīng)完成了優(yōu)化機(jī)制;瀏覽器會(huì)自動(dòng)將狀態(tài)一致的元素做樣式快照。狀態(tài)一致就是要滿足以下幾個(gè)條件:
- 沒(méi)有設(shè)定 ID
 - tag 及 class 必須完全一致
 - 沒(méi)有設(shè)定 style 屬性
 - 樣式規(guī)則中不能使用各種同級(jí)選擇器(例如:〜,+,:first-child 等)
 
由于上面的條件,以及前面討論到的 CSS 運(yùn)算過(guò)程,編寫 CSS 時(shí)也有幾個(gè)地方可以稍微留心一下:
- 由于樣式規(guī)則的目標(biāo)屬性會(huì)分組存放,id 選擇器效率非常高,所以是不能與其他條件混用的。
 - 不要寫過(guò)深的 CSS 樣式規(guī)則
 - 能不用 inline style 就不要用,除了難以維護(hù)外,由于是存在于 DOM 樹(shù)上,無(wú)法預(yù)先與其他樣式合并計(jì)算,所以效率也會(huì)大打折扣
 
如果能夠注意到這類典型的小細(xì)節(jié),CSS 效率自然也可以大幅提升。



















 
 
 








 
 
 
 