在開始前,我們必須了解一個(gè)真相:為什么排版引擎解析 CSS 選擇器時(shí)一定要從右往左析?
1.HTML 經(jīng)過(guò)解析生成 DOM Tree(這個(gè)我們比較熟悉);而在 CSS 解析完畢后,需要將解析的結(jié)果與 DOM Tree 的內(nèi)容一起進(jìn)行分析建立一棵 Render Tree,最終用來(lái)進(jìn)行繪圖。Render Tree 中的元素(WebKit 中稱為「renderers」,F(xiàn)irefox 下為「frames」)與 DOM 元素相對(duì)應(yīng),但非一一對(duì)應(yīng):一個(gè) DOM 元素可能會(huì)對(duì)應(yīng)多個(gè) renderer,如文本折行后,不同的「行」會(huì)成為 render tree 種不同的 renderer。也有的 DOM 元素被 Render Tree 完全無(wú)視,比如 display:none 的元素。
2.在建立 Render Tree 時(shí)(WebKit 中的「Attachment」過(guò)程),瀏覽器就要為每個(gè) DOM Tree 中的元素根據(jù) CSS 的解析結(jié)果(Style Rules)來(lái)確定生成怎樣的 renderer。對(duì)于每個(gè) DOM 元素,必須在所有 Style Rules 中找到符合的 selector 并將對(duì)應(yīng)的規(guī)則進(jìn)行合并。選擇器的「解析」實(shí)際是在這里執(zhí)行的,在遍歷 DOM Tree 時(shí),從 Style Rules 中去尋找對(duì)應(yīng)的 selector。
3.因?yàn)樗袠邮揭?guī)則可能數(shù)量很大,而且絕大多數(shù)不會(huì)匹配到當(dāng)前的 DOM 元素(因?yàn)閿?shù)量很大所以一般會(huì)建立規(guī)則索引樹),所以有一個(gè)快速的方法來(lái)判斷「這個(gè) selector 不匹配當(dāng)前元素」就是極其重要的。
4.如果正向解析,例如「div div p em」,我們首先就要檢查當(dāng)前元素到 html 的整條路徑,找到最上層的 div,再往下找,如果遇到不匹配就必須回到最上層那個(gè) div,往下再去匹配選擇器中的第一個(gè) div,回溯若干次才能確定匹配與否,效率很低。
5.逆向匹配則不同,如果當(dāng)前的 DOM 元素是 div,而不是 selector 最后的 em,那只要一步就能排除。只有在匹配時(shí),才會(huì)不斷向上找父節(jié)點(diǎn)進(jìn)行驗(yàn)證。
6.但因?yàn)槠ヅ涞那闆r遠(yuǎn)遠(yuǎn)低于不匹配的情況,所以逆向匹配帶來(lái)的優(yōu)勢(shì)是巨大的。同時(shí)我們也能夠看出,在選擇器結(jié)尾加上「*」就大大降低了這種優(yōu)勢(shì),這也就是很多優(yōu)化原則提到的盡量避免在選擇器末尾添加通配符的原因。
簡(jiǎn)單的來(lái)說(shuō)瀏覽器從右到左進(jìn)行查找的好處是為了盡早過(guò)濾掉一些無(wú)關(guān)的樣式規(guī)則和元素
<div> <div class="Aaron"> <p><span>s1</span></p> <p><span>s2</span></p> <p><span>s3</span></p> <p><span class='red'>s4</span></p> </div> </div>
CSS選擇器
div > div.Aaron p span.red{ color:red; }
而如果按從左到右的方式進(jìn)行查找:
1. 先找到所有div節(jié)點(diǎn)
2. 第一個(gè)div節(jié)點(diǎn)內(nèi)找到所有的子div,并且是class="Aaron"
3. 然后再一次匹配p span.red
等情況
4. 遇到不匹配的情況,就必須回溯到一開始搜索的div或者p節(jié)點(diǎn),然后去搜索下個(gè)節(jié)點(diǎn),重復(fù)這樣的過(guò)程。這樣的搜索過(guò)程對(duì)于一個(gè)只是匹配很少節(jié)點(diǎn)的選擇器來(lái)說(shuō),效率是極低的,因?yàn)槲覀兓ㄙM(fèi)了大量的時(shí)間在回溯匹配不符合規(guī)則的節(jié)點(diǎn)。
如果換個(gè)思路,我們一開始過(guò)濾出跟目標(biāo)節(jié)點(diǎn)最符合的集合出來(lái),再在這個(gè)集合進(jìn)行搜索,大大降低了搜索空間。
從右到左來(lái)解析選擇器:
則首先就查找到<span class='red'>的元素。
Firefox稱這種查找方式為key selector(關(guān)鍵字查詢),所謂的關(guān)鍵字就是樣式規(guī)則中最后(最右邊)的規(guī)則,上面的key就是span.red。
緊接著我們判斷這些節(jié)點(diǎn)中的前兄弟節(jié)點(diǎn)是否符合p這個(gè)規(guī)則,這樣就又減少了集合的元素,只有符合當(dāng)前的子規(guī)則才會(huì)匹配再上一條子規(guī)則。
要知道DOM樹是一個(gè)什么樣的結(jié)構(gòu),一個(gè)元素可能有若干子元素,如果每一個(gè)都去判斷一下顯然性能太差。而一個(gè)子元素只有一個(gè)父元素,所以找起來(lái)非常方便。你可以看看CSS的選擇器的設(shè)計(jì),完全是為了優(yōu)化從子元素找父元素而決定的。
打個(gè)比如 p span.showing
你認(rèn)為從一個(gè)p元素下面找到所有的span元素并判斷是否有class showing快,還是找到所有的span元素判斷是否有class showing并且包括一個(gè)p父元素快 ?
所以瀏覽器解析CSS的引擎就是用這樣的算法。
請(qǐng)驗(yàn)證,完成請(qǐng)求
由于請(qǐng)求次數(shù)過(guò)多,請(qǐng)先驗(yàn)證,完成再次請(qǐng)求
打開微信掃碼自動(dòng)綁定
綁定后可得到
使用 Ctrl+D 可將課程添加到書簽
舉報(bào)