CSS選擇器渲染效率

木頭先生發表於2017-04-19

1 瀏覽器如何識別你的選擇器

首先我們需要清楚,瀏覽器是如何讀取選擇器,以識別樣式,並將相應的樣式附於對應的HTML元素,達到美化頁面的效果。Chris Coyier曾在《Efficiently Rendering CSS》一文中說過“瀏覽器讀取你的選擇器,遵循的原則是從選擇器的右邊到左邊讀取。換句話說,瀏覽器讀取選擇器的順序是由右到左進行”。比如說:

div.nav < ul li a[title]

上面的例項來說,瀏覽器首先會嘗試在你的HTML標籤中尋找“a[title]”元素,接著在匹配“li和ul”,最後在去匹配“div.nav”。這就是前成所主的“選擇器從右到左的原則”。

選擇器的最後一部分,也就是選擇器的最右邊(在這個例子中就是a[title]部分)部分被稱為“關鍵選擇器”,它將決定你的選擇器的效率如何?是高還是低。

那麼如何讓關鍵選擇器更有效,效能化更高呢?其實很簡單,主要把握一點“越具體的關鍵選擇器,其效能越高”

 

2 CSS選擇器的效率
如 果你閱讀了本站的有關於選擇器型別的介紹的話,你對選擇器並不會感到陌生。就算你沒讀過,我想CSS選擇器不會讓我們覺得是新東西,比如我們常用的基本選 擇器“元素標籤選擇器div”、“id選擇器#header”、“類選擇器.class”,或者說我們很少見的偽類選擇器“:focus”以及更復雜的 css3選擇器“:nth-child”等等。
選擇器有一個固有的效率,我們來看Steve Souders給排的一個順序:
id選擇器(#myid)
類選擇器(.myclassname)
標籤選擇器(div,h1,p)
相鄰選擇器(h1+p)
子選擇器(ul > li)
後代選擇器(li a)
萬用字元選擇器(*)
屬性選擇器(a[rel=”external”])
偽類選擇器(a:hover,li:nth-child)
上面九種選擇器的效率是從高到低排下來的,基中ID選擇器的效率是最高,而偽類選擇器的效率則是最低。

我們不得不提的是,縱使ID選擇器很快、高效,但是它也僅僅如此。從Steve Souders的CSS Test我們可以看ID選擇器和類選擇器在速度上的差異很小很小

我們知道ID’s 是最高效的選擇器。當你想讓渲染速度最高效時,你可能會給每個獨立的標籤配置一個ID,然後用這些ID寫樣式。那會超級快,也超級荒唐。這樣的結果是語義 極差,維護難到了極點。即使在核心部分你也不應該見過這樣做的。我認為這個可以提醒我們不要為了高效的CSS放棄語義和可維護性

 

3 書寫規範

A 不要用標籤修飾ID

死也不要像下 面這樣幹:

ul#main-navigation { }

ID’s 是唯一的,所以不需要用標籤修飾,這隻會讓它更低效。

如果你可以避免的話,也不要用它修飾 class 。class 不是唯一的,所以理論上你可以把它用在不同的標籤。如果你願意的話,你可以用標籤控制不同的樣式,這樣你可能需要標籤修飾(比如:li.first),但 這樣做的人很少,所以,don’t .

B 絕對沒有比用後代選擇器更糟糕的做法了

David Hyatt:
後代選擇器是CSS裡最昂貴的選擇器,昂貴得可怕——特別是當它放在標籤和通用符後面時。
就如下面這個東東一樣,絕對的效 率毒瘤:

html body ul li a { }

C 一個選擇器渲染失敗比這個選擇器被渲染更高效

我不是很確定是否有更好的證據去證明這一點,因為如果你有大量的選擇 器在CSS樣式表裡無法找到,這樣的事情貌似很離奇,但一點必需注意的是,從右到左的解釋一個選擇器來說,一旦它找不到,那它就會停止嘗試。然而如果它找 到了,那它就需要花更多精力去解釋了。

D 試想一下為何你這樣寫選擇器

思考 下這東東:

#main-navigation li a { font-family: Georgia, Serif; }

你可能不需要從 a 選擇器開始(如果你只是想換個字型)。下面這個可能更高效些:

#main-navigation { font-family: Georgia, Serif; }

E 不要過度限制選擇器

 擁有一個明確的關鍵選擇器最大的好處就是你可以避免使用過度限制選擇器。一個過度限制選擇器可能像:

html body .wrapper #content a {}

這裡的寫的太多了,至少3個選擇器是完全不需要的。它可以最多像這個樣子:

#content a {}

一些更現實的例子:#nav li a{}變成這個:#nav a {}

我們知道如果a在li裡面,它也必定在#nav裡面,所有我們可以馬上把li從選擇器組中拿掉。然後,既然我們知道在頁面中只有一個ID為nav的元素,那麼它依附的元素就是完全沒有關係得了,我們也可以拿掉ul

過度限制選擇器使瀏覽器工作比它實際需要的更繁重,花費的時間更多。我們可以刪掉不必需的限制,來使我們的選擇器更簡單和高效。

 

4 案例詳解:

假設你有一個複雜的頁面,它相當巨大並且在你的一個很大很大的站點上。在那個頁面上有成百上千甚至上萬的 a 標籤。它還有一個小的社交連結區域放在一個ID為#social的Ul裡面。我們假設它們是Twitter,Facebook,Dribbble還有 Google+的連結吧。在這個頁面上我們有四個社交連結和成百上千的其他連結。 下面的這個選擇器就自然的不是那麼高效和合理了:#social a {…}

這裡發生的情況是瀏覽器會在定位到#social區域下的四個連結之前得到頁面上所有成千上萬的連結。我們的關鍵選擇器匹配了太多我們不感興趣的其他元素。

為了補救我們可以給每個在社交連結區域的 a 增加一個更特殊、明確的選擇器 .social-link , 但是這好像有點違揹我們的認知:當我們能用組合選擇器的時候就不要放不必要的類標示在元素上。

這就是為什麼我對選擇器的效能如此感興趣的原因了:必須在web 標準最佳實踐和速度之間的保持平衡。

<ul id="social">
    <li><a href="#" class="twitter">Twitter</a></li>
    <li><a href="#" class="facebook">Facebook</a></li>
    <li><a href="#" class="dribble">Dribbble</a></li>
    <li><a href="#" class="gplus">Google+</a></li>
</ul>

CSS:#social a {}

改變後:

<ul id="social">
    <li><a href="#" class="social-link twitter">Twitter</a></li>
    <li><a href="#" class="social-link facebook">Facebook</a></li>
    <li><a href="#" class="social-link dribble">Dribbble</a></li>
    <li><a href="#" class="social-link gplus">Google+</a></li>
</ul>

CSS:#social .social-link {}

這個新的關鍵選擇器將會匹配更少的元素,這意味著瀏覽器能夠很快的找到它們並渲染特定的樣式,然後專注於下一件事。

另外,事實上我們可以用.social-link{}更清晰的選擇,而不是過分限制它。