編寫高效的 CSS 選擇器 – CSS魔法
高效的CSS已經不是一個新的話題了,也不是我一個非得重拾的話題,但它卻是我在工作之時,所感興趣的,關注已久的話題。
有很多人都忘記了,或在簡單的說沒有意識到,CSS在我們手中,既能很高效,也可以變得很低能。這很容易被忘記,尤其是當你意識到你會的太少,CSS程式碼效率很低的時候。
下面的規則只真正被應用到那些速度要求很高,有成百上千的DOM元素被繪製在頁面上的大型網站。但是,實踐出真理,多知道一點總是好的。
CSS 選擇器
對我們大多數人來說,CSS選擇器並不陌生。最基本的選擇器是元素選擇器(比如div),ID選擇器(比如#header)還有類選擇器(比如.tweet)。
一些的不常見的選擇器包括偽類選擇器(:hover),很多複雜的CSS3和正則選擇器,比如:first-child,class ^= “grid-”。
CSS選擇器具有高效的繼承性, CSS選擇器效率從高到低的排序如下:
- ID選擇器 比如#header
- 類選擇器 比如.promo
- 元素選擇器 比如 div
- 兄弟選擇器 比如 h2 + p
- 子選擇器 比如 ul > li
- 後代選擇器 比如 ul a
- 通用選擇器 比如 *
- 屬性選擇器 比如 type = “text”
- 偽類/偽元素選擇器 比如 a:hover
我們不得不提的是,縱使ID選擇器很快、高效,但是它也僅僅如此。從CSS Test我們可以看出 ID選擇器 和 類選擇器 在速度上的差異很小很小。
在Windows系統上的Firefox 6上,我測得了一個簡單類選擇器的(reflow figure)重繪速度為10.9ms,而ID選擇器為12.5ms,所以事實上ID比類選擇器重繪要慢一點點。
ID選擇器和類選擇器在速度上的差異基本上沒有關係。
在一個標籤選擇器(a)的測試上顯示,它比類或ID選擇器的速度慢了很多。在一個巢狀很深的後代選擇器的測試上,顯示資料為440左右!從這裡我們可以看出ID/類選擇器 和 元素/後代選擇器中間的差異較大,但是相互之間的差異較小。
組合選擇器
你可以有一個標準的選擇器比如 #nav,來選擇任何帶有ID為”nav”的元素,或在你可以有一個組合選擇器比如#nav a,來選擇任何在ID為’nav’的元素裡面的連結元素
此刻,我們讀這些是從左到右的方式。我們是先找到#nav,然後從它的裡面找其他元素。但是瀏覽器解析這些不是這樣的:瀏覽器解析選擇器是從右到左的方式。
在我們看來,#nav裡面帶了一個a,瀏覽器卻是看到的a在#nav裡面。這些細微的差異對選擇器的效率有很大的影響,同時學這些差異也是很有價值的。
瀏覽器從最右邊的元素開始(它想要渲染的元素),然後用它的方式回溯DOM樹比從DOM樹的最高層開始選擇向下尋找。
這些對CSS選擇器的效率有很大的影響。
關鍵選擇器
關鍵選擇器,正如前面討論的一樣,是一個複雜的CSS選擇器中最右邊部分。它是瀏覽器最先尋找的。
現在我們回到討論開始的地方,哪類選擇器是最高效的?哪個是會影響選擇器效率的關鍵選擇器;寫CSS程式碼的時候,關鍵選擇器是能否高效的決定因素。 一個關鍵CSS選擇器像這樣:
#content .intro {}
是不是高效選擇器比如類選擇器天生就高效?瀏覽器會尋找.intro的例項(可能會很多),然後沿著DOM樹向上查詢,確定剛才找到的例項是否在一個帶有ID為”content”的容器裡面。
但是,下面的選擇器就表現的不是那麼好了:
#content * {}
這個選擇器所做的是選擇所有在頁面上的單個元素(是每個單個的元素),然後去看看它們是否有一個 #content 的父元素。這是一個非常不高效選擇器因為它的關鍵選擇器執行開銷太大了。
運用這些知識我們就可以在分類和選擇元素的時候做出更好的選擇。
假設你有一個複雜的頁面,它相當巨大並且在你的一個很大很大的站點上。在那個頁面上有成百上千甚至上萬的 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{}更清晰的選擇,而不是過分限制它。閱讀下一部分你會知道原因…
簡單的重述一次,你的關鍵選擇器會決定瀏覽器的工作量,因此,我們應該重視一下關鍵選擇器。
過度限制選擇器
現在我們知道了什麼是關鍵選擇器,還有它工作的原理,但是我們可以更樂觀一點。擁有一個明確的關鍵選擇器最大的好處就是你可以避免使用過度限制選擇器。一個過度限制選擇器可能像:
html body .wrapper #content a {}
這裡的寫的太多了,至少3個選擇器是完全不需要的。它可以最多像這個樣子:
#content a {}
這會發生什麼呢? 首先第一個意味著瀏覽器不得不尋找所有的 a 元素,然後檢查他們是否在一個ID為”content”的元素中,然後如此迴圈直到HTML標籤。這樣造成了太多的我們不太想要的花費。瞭解了這個,我們得到一些更現實的例子:
#nav li a {}
變成這個:
#nav a {}
我們知道如果a在li裡面,它也必定在#nav裡面,所以我們可以馬上把li從選擇器組中拿掉。然後,既然我們知道在頁面中只有一個ID為nav的元素,那麼它依附的元素就是完全沒有關係得了,我們也可以拿掉ul。
過度限制選擇器使瀏覽器工作比它實際需要的更繁重,花費的時間更多。我們可以刪掉不必需的限制,來使我們的選擇器更簡單和高效。
這些真的需要嗎?
最短的答案是:或許不是。
最長的答案是:它取決於你正在搭建的站點。如果你正在為你的晉升而努力,那麼就好好寫出簡單、高效的CSS程式碼吧,因為你可能不會感覺到它給你帶來的改變。 如果你正在搭建下一個每個頁面都以毫秒計算的網站,這樣有時速度會很快,但有時可能不是。
瀏覽器將會在解析CSS的速度上變得更好,甚至在手機端。在一個網站上,你不太可能會覺察到一個低效的CSS選擇器,但是….
但是
它確實發生了,瀏覽器還是不得不去做我們討論的所有工作,無論它們變得多快。即使你不需要或者甚至不想實踐任何一個,但是它都是我們值得學習的知識。請記住選擇器可能會讓你付出很大代價,你應該避免盯著一個看。這意味著如果你發現你自己在寫像這樣的:
div:nth-of-type(3) ul:last-child li:nth-of-type(odd) * { font-weight:bold }
這時,你可能就做錯了。