本文的目標(biāo)讀者是入門(mén)級(jí)Web前端開(kāi)發(fā)人員。
本文介紹了CSS選擇符表達(dá)式引擎的基本原理。CSS選擇符引擎幾乎是前端開(kāi)發(fā)人員每天在使用的工具。本文將逐一介紹實(shí)現(xiàn)該引擎的各種策略。首先,我們介紹基于W3C標(biāo)準(zhǔn)API的方法。
W3C標(biāo)準(zhǔn)的Slectors API
能夠支持的平臺(tái): Safari 3+, Firefox 3.1+, Internet Explorer 8+, Chrome and Opera 10+
兩個(gè)最常用的方法:
querySelector,該函數(shù)接受一個(gè)CSS選擇符字符串,返回找到的第一個(gè)元素,如果沒(méi)有找到則返回null。
querySelectorAll,該函數(shù)接受一個(gè)CSS選擇符字符串,返回找到的所有元素的集合(NodeList)。
這兩個(gè)方法存在于所有的DOM元素,DOM文檔對(duì)象,以及DOM文檔片段(fragment)對(duì)象上。
<div id="test"> <b>Hello</b>, I'm a ninja! </div> <div id="test2"></div>
<script> window.onload = function () { var divs = document.querySelectorAll("body > div"); assert(divs.length === 2, "Two divs found using a CSS selector."); var b = document.getElementById("test") .querySelector("b:only-child"); assert(b, "The bold element was found relative to another element."); }; </script>
上述例子的一個(gè)缺點(diǎn)是它依賴(lài)于瀏覽器對(duì)CSS選擇符的支持(老版本IE就歇菜了),因此可以考慮使用以某元素作為根節(jié)點(diǎn)對(duì)子節(jié)點(diǎn)的查詢(xún)。代碼如下。
<script> window.onload = function () { var b = document.getElementById("test").querySelector("div b"); assert(b, "Only the last part of the selector matters."); }; </script>
上述代碼有個(gè)問(wèn)題,當(dāng)以某元素作為根節(jié)點(diǎn)對(duì)子節(jié)點(diǎn)的查詢(xún)時(shí),query函數(shù)只檢查最右邊的部分是不是包含在父節(jié)點(diǎn)里。注意到#test下面壓根就沒(méi)有div標(biāo)簽,可是query函數(shù)忽略了查詢(xún)字符串的前面部分。
這個(gè)現(xiàn)象確實(shí)有悖于我們期望的CSS選擇符引擎的運(yùn)行效果,所以我們需要做一些修補(bǔ)工作。最常見(jiàn)的技巧是:臨時(shí)增加一個(gè)新id給那個(gè)根節(jié)點(diǎn)元素從而強(qiáng)行地包含它里面的內(nèi)容。代碼如下。
<script> (function () { var count = 1; this.rootedQuerySelectorAll = function (elem, query) { var oldID = elem.id; elem.id = "rooted" + (count++); try { return elem.querySelectorAll("#" + elem.id + " " + query); } catch (e) { throw e; } finally { elem.id = oldID; } }; })(); window.onload = function () { var b = rootedQuerySelectorAll( document.getElementById("test"), "div b"); assert(b.length === 0, "The selector is now rooted properly."); }; </script>
在上述代碼中我們需要注意到以下幾點(diǎn):
首先,要給父元素一個(gè)全局唯一的id,因此需要保存父元素原始的id。然后把這個(gè)全局唯一的id添加到查詢(xún)字符串中。
接著的收尾部分就是去除新增加的那個(gè)id和返回查詢(xún)結(jié)果,這個(gè)過(guò)程中可能會(huì)有一個(gè)API異常拋出(多數(shù)情況是因?yàn)檫x擇符語(yǔ)法錯(cuò)誤或者是瀏覽器不支持的選擇符)。因此,我們要在外層用try/catch語(yǔ)句塊包住API調(diào)用語(yǔ)句,還要在finnally的子句中還原父元素的原始id。你可能會(huì)發(fā)現(xiàn),這里隱藏著JavaScript語(yǔ)言神奇的一個(gè)地方,就是雖然我們?cè)趖ry語(yǔ)句里已經(jīng)return了,可是finnally子句還是要被執(zhí)行的(在結(jié)果值被真正return給調(diào)用函數(shù)前)。
選擇器API絕對(duì)可以算作W3C標(biāo)準(zhǔn)里最具前途的新API了。一旦主流瀏覽器支持CSS3(或至少絕大部分CSS3選擇符)以后,它可以節(jié)省編程人員使用大量的JavaScript代碼。
使用 XPath 尋找元素
XPath是一種可以在DOM文檔中查詢(xún)節(jié)點(diǎn)的語(yǔ)言。它甚至比CSS選擇符更加強(qiáng)大。許多流行的瀏覽器 (Firefox,Safari 3+, Opera 9+, Chrome)都提供了對(duì)XPath的部分函數(shù)實(shí)現(xiàn),可以在HTML文檔中查找元素。 Internet Explorer 6及之前的版本只能使用XPath查找XML文檔(而不是HTML文檔)。
Xpath表達(dá)式比復(fù)雜的CSS選擇符執(zhí)行快。但是,當(dāng)我們實(shí)現(xiàn)一個(gè)純DOM操作方式的CSS選擇符引擎時(shí),我們要考慮瀏覽器支持性的風(fēng)險(xiǎn)。在對(duì)于簡(jiǎn)單的CSS選擇符,Xpath就失去優(yōu)越性了。
因此我們考慮使用一個(gè)閾值,當(dāng)使用Xpath更有利的情況下我們就是用Xpath。決定閾值依賴(lài)于開(kāi)發(fā)人員的經(jīng)驗(yàn),比如:當(dāng)查找id或者標(biāo)簽時(shí),使用純DOM操作代碼永遠(yuǎn)是更快的方式。
如果用戶(hù)瀏覽器支持Xpath表達(dá)式,我們可以使用下述代碼(依賴(lài)于prototype庫(kù))。
if (typeof document.evaluate === "function") { function getElementsByXPath(expression, parentElement) { var results = []; var query = document.evaluate(expression, parentElement || document, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null); for (var i = 0, length = query.snapshotLength; i < length; i++) results.push(query.snapshotItem(i)); return results; } }
雖然使用Xpath可以解決任何選擇符問(wèn)題,但它不是一個(gè)可行的方案。對(duì)于一個(gè)的CSS選擇符表達(dá)式,對(duì)應(yīng)的Xpath的表達(dá)式卻是令人生畏的復(fù)雜。 下面這個(gè)表格展示了如何把CSS選擇符轉(zhuǎn)換到Xpath表達(dá)式。
當(dāng)建造一個(gè)基于正則表達(dá)式的CSS選擇符引擎時(shí),我們可以包含Xpath的方式作為一個(gè)子模塊,它把用戶(hù)查詢(xún)的CSS選擇符表達(dá)式部分轉(zhuǎn)換成Xpath的表達(dá)式,然后使用Xpath的辦法查找DOM。
實(shí)現(xiàn)XPath的部分的代碼可能與正則表達(dá)式方式的代碼一樣多。很多開(kāi)發(fā)人員選擇拋棄XPath的部分來(lái)減少CSS選擇符引擎的復(fù)雜程度。所以,你需要衡量Xpath帶來(lái)的的性能提升以及它的代碼實(shí)現(xiàn)復(fù)雜程度。
純DOM實(shí)現(xiàn)方式
CSS選擇符引擎的核心是以純DOM操作方式實(shí)現(xiàn)的。它解析用戶(hù)給出的CSS選擇符,然后使用已有的DOM方法(例如getElementById, getElementsByTagName)來(lái)查找對(duì)應(yīng)的DOM元素。使用純DOM方式實(shí)現(xiàn)有以下理由:
第一,Internet Explorer 6 and 7。盡管IE8以上的版本支持querySelectorAll()方法,但是在IE6、7中對(duì)Xpath和選擇符API的支持使得使用純DOM實(shí)現(xiàn)很有必要。
第二,向下兼容,如果你希望你的代碼能夠“降級(jí)”支持老版本的瀏覽器(比如Safari 2),那么你應(yīng)該使用純DOM實(shí)現(xiàn)。
第三,為了速度。對(duì)于某些CSS選擇符表達(dá)式,使用純DOM核心能夠處理得更快(比如根據(jù)id找元素)。
知道了使用純DOM核心的重要性,接下來(lái)我們要看以?xún)煞N方式實(shí)現(xiàn)選擇符引擎:從上往下解析,和從下往上解析。
一個(gè)從上往下的引擎是這樣解析CSS選擇符表達(dá)式的:從左往右的匹配元素,在前部分的基礎(chǔ)上再接著找下部分匹配的元素。 這種方式是目前主流JavaScript庫(kù)的實(shí)現(xiàn)方式,更通用地,也是尋找頁(yè)面元素的最佳方式。 讓我們來(lái)看一段標(biāo)記
<body> <div></div> <div class="ninja"> <span>Please </span> <a href="/ninja"><span>Click me!</span> </a> </div> </body>
如果我們想選"Click me!"那個(gè)元素,我們可以這樣寫(xiě) 選擇符表達(dá)式 : div.ninja a span 。
使用從上往下的方法是這樣解析這個(gè)選擇符表達(dá)式的:
表達(dá)式中的第一項(xiàng),div.ninja 指明了文檔中的一顆子樹(shù)。在那顆子樹(shù)中,接著找表達(dá)式中下一項(xiàng)對(duì)應(yīng)的子樹(shù)。最后,span的目標(biāo)節(jié)點(diǎn)被找到。
注意,這只是最簡(jiǎn)單的情況。在任何層的推進(jìn)過(guò)程中,完全有可能有多顆子樹(shù)同時(shí)匹配表達(dá)式。在實(shí)現(xiàn)選擇符引擎的時(shí)候,有兩個(gè)原則需要考慮:
返回的結(jié)果中元素的順序應(yīng)該按照在文檔中原本的順序出現(xiàn)
返回的結(jié)果中的元素不應(yīng)該有重復(fù)的(比如不能一個(gè)元素在結(jié)果中出現(xiàn)兩次)
為了避免這些陷阱,具體的代碼實(shí)現(xiàn)可能會(huì)有一點(diǎn)小技巧。下面是一個(gè)簡(jiǎn)化的top-down方式引擎,它只能夠支持按照tag標(biāo)簽名字查找元素。
<div> <div> <span>Span</span> </div> </div> <script> window.onload = function () { function find(selector, root) { root = root || document; var parts = selector.split(" "), query = parts[0], rest = parts.slice(1).join(" "), elems = root.getElementsByTagName(query), results = []; for (var i = 0; i < elems.length; i++) { if (rest) { results = results.concat(find(rest, elems[i])); } else { results.push(elems[i]); } } return results; } var divs = find("div"); assert(divs.length === 2, "Correct number of divs found."); var divs = find("div", document.body); assert(divs.length === 2, "Correct number of divs found in body."); var divs = find("body div"); assert(divs.length === 2, "Correct number of divs found in body."); var spans = find("div span"); assert(spans.length === 2, "A duplicate span was found."); }; </script>
在上面的例子中,我們實(shí)現(xiàn)了一個(gè)簡(jiǎn)單的支持按照tag標(biāo)簽名字查找元素的從上往下解析方式的選擇符引擎。 這個(gè)引擎可以分解為幾個(gè)子部分:解析選擇符表達(dá)式,在文檔中查找元素,過(guò)濾元素,遞歸/合并每一層里的結(jié)果。
解析選擇符表達(dá)式
在上面的例子中,解析過(guò)程就是把CSS選擇符(例如"div span")分解成字符串?dāng)?shù)組(["div", "span"])。實(shí)際上,在CSS2和CSS3的標(biāo)準(zhǔn)中,使用屬性值查找元素是被支持的。因此在選擇符中完全有可能有額外的空格,使得上面簡(jiǎn)單的方法不可行了。但是,這種簡(jiǎn)單的方法對(duì)于處理大部分的情況已經(jīng)足夠了。
要完全實(shí)現(xiàn)解析,我們需要一系列的解析規(guī)則來(lái)處理用戶(hù)給出的任何表達(dá)式。 下面的代碼就是使用正則表達(dá)式把表達(dá)式分解成各個(gè)小塊(如果需要,則分開(kāi)逗號(hào))
<script type="text/javascript"> var selector = "div.class > span:not(:first-child) a[href]" var chunker = /((?:\([^\)]+\)|\[[^\]]+\]|[^ ,\(\[]+)+)(\s*,\s*)?/g; var parts = []; // Reset the position of the chunker regexp (start from beginning) chunker.lastIndex = 0; // Collect the pieces while ((m = chunker.exec(selector)) !== null) { parts.push(m[1]); // Stop if we've countered a comma if (m[2]) { extra = RegExp.rightContext; break; } } assert(parts.length == 4, "Our selector is broken into 4 unique parts."); assert(parts[0] === "div.class", "div selector"); assert(parts[1] === ">", "child selector"); assert(parts[2] === "span:not(:first-child)", "span selector"); assert(parts[3] === "a[href]", "a selector"); </script>
顯然,這段代碼支持的選擇符只是一張大拼圖中的一小部分。我們需要定義更多的解析規(guī)則來(lái)支持用戶(hù)輸入的各種表達(dá)式組合。絕大多數(shù)CSS選擇符引擎使用了map結(jié)構(gòu),來(lái)將正則表達(dá)式對(duì)應(yīng)到目標(biāo)處理函數(shù)。這樣當(dāng)一個(gè)正則表達(dá)式匹配用戶(hù)表達(dá)式的一部分時(shí),對(duì)應(yīng)的函數(shù)就去處理那一部分表達(dá)式的選擇符。
尋找元素
在頁(yè)面中尋找正確的DOM元素有許多種解決方案。使用哪種方案取決于瀏覽器支持什么樣的選擇符。
首先是 getElementById() 方法。它只在HTML文檔的根節(jié)點(diǎn)上存在。它的作用是找到第一個(gè)匹配指定id值的元素,因此他可以用來(lái)解決 "#id" 這樣的表達(dá)式。注意,在Internet Explorer 和 Opera,它同樣也會(huì)查找第一個(gè)具有同名 name值的元素。 因此,如果需要值按照 id 值查找,我們需要額外的一步驗(yàn)證工作來(lái)排除掉 name值同名的元素。
如果需要支持尋找所有具有給定 id值的元素(這在CSS選擇符表達(dá)式中是習(xí)慣性用法,盡管HTML文法規(guī)定一個(gè)id只能對(duì)應(yīng)一個(gè)元素),有兩種方法可以采用:第一種方法,遍歷所有的元素,找出所有匹配給定 id值的元素;第二種方法,使用 document.all["id"] ,它將返回一個(gè)包含匹配id值元素的數(shù)組。
接下來(lái)是 getElementsByTagName() 方法,它的作用就如同它的名字所述:找出所有匹配給定標(biāo)簽名的元素。 注意,它還有另一種用法:如果使用 星號(hào)* 作為參數(shù)標(biāo)簽名,那么它會(huì)返回文檔中或者一個(gè)節(jié)點(diǎn)下所有的元素。 這一招對(duì)于 處理基于屬性值的選擇符 很有用,比如 ".class" 或者 "[attr]"。 因?yàn)?nbsp;".class" 并沒(méi)有指定標(biāo)簽名,所以我們需要列出某節(jié)點(diǎn)下的所有子元素,然后依次判斷class名稱(chēng)。
另外,在Internet Explorer中使用 星號(hào)* 查找有一個(gè)缺點(diǎn),它同樣會(huì)返回 注釋語(yǔ)句 節(jié)點(diǎn)(因?yàn)樵贗E中,注釋語(yǔ)句節(jié)點(diǎn)有一個(gè) "!" 的標(biāo)簽名,所以它也會(huì)被返回)。 這樣,我們需要額外的一步過(guò)濾工作來(lái)排除注釋語(yǔ)句節(jié)點(diǎn)。
接下來(lái)是 getElementsByName() 方法,它的作用只有一個(gè): 找出所有匹配給定 name值的節(jié)點(diǎn)(例如,<input> 元素都具有name值 )。因此這個(gè)方法可以用來(lái)解決 "[name=name]" 這樣的表達(dá)式。
最后是 getElementsByClassName() 方法。這個(gè)方法相對(duì)比較新,正在被各主流瀏覽器實(shí)現(xiàn)(Firefox 3+, Safari 3+ 和 Chrome)。它的作用是基于 元素的class名稱(chēng) 進(jìn)行查找。 這種瀏覽器原生的方法極大地加快了 按class名稱(chēng)查找 的代碼實(shí)現(xiàn)。
盡管還有一些其他技巧用來(lái)解決元素查找,以上那些方法依然是我們主要使用的工具。一旦找出了所有匹配的備選元素后,接下來(lái)就進(jìn)行元素過(guò)濾了。
過(guò)濾元素
一個(gè)CSS表達(dá)式通常是由幾個(gè)獨(dú)立的小部分組成的。例如,這樣一個(gè)表達(dá)式 "div.class[id]" 就由三個(gè)部分組成: 1. div元素 2. 具有給定的class名稱(chēng) 3. 具有一個(gè)名叫id的屬性值。
首先我們需要找出選擇符的第一部分。例如,在上述表達(dá)式中,我們看到第一部分是找div元素,所以我們立刻想到用 getElementsByTagName() 方法找出頁(yè)面上所有的 <div> 元素。 接下來(lái),我們必須過(guò)濾元素,使得剩下的元素具有給定的class名稱(chēng)和id屬性值。
過(guò)濾元素是選擇符引擎的實(shí)現(xiàn)中普遍存在的一部分。過(guò)濾原則主要依靠元素屬性值或者元素在DOM樹(shù)中與其他節(jié)點(diǎn)的關(guān)系。
按照屬性過(guò)濾:訪問(wèn)元素的DOM屬性(通常使用 getAttribute() 方法),并且驗(yàn)證它的值是否等于給定值。按照class類(lèi)名過(guò)濾是本類(lèi)別中的一個(gè)子集(訪問(wèn) className 屬性并且驗(yàn)證它的值)。
按照位置關(guān)系過(guò)濾: 這種情況出現(xiàn)在對(duì)于在某父元素上使用 ":nth-child(even)" 或者 ":last-child" 組合的表達(dá)式。 如果瀏覽器支持這樣的CSS選擇符,那么會(huì)返回一個(gè)子元素的集合。另外,所有的瀏覽器都支持 childNodes,它返回一個(gè)子元素的集合,其中也包含所有的純文本節(jié)點(diǎn)和注釋語(yǔ)句節(jié)點(diǎn)。 使用以上兩種方法,可以按照元素在DOM樹(shù)中的位置關(guān)系進(jìn)行過(guò)濾。
實(shí)現(xiàn)元素過(guò)濾功能具有兩個(gè)目的:第一,可以把這個(gè)功能提供給用戶(hù)讓他們測(cè)試任意元素是否符合某值;第二,在內(nèi)部計(jì)算時(shí),可以檢查元素是否符合用戶(hù)給出的選擇符表達(dá)式。
合并元素
在本文的第一段代碼中,我們可以看見(jiàn)選擇符引擎需要能夠遞歸的找元素(找出后代元素)以及合并所有符合要求的元素,最終返回結(jié)果集。
但是,在本小節(jié)中,我們初步的代碼實(shí)現(xiàn)太簡(jiǎn)單了。注意到,我們最終在文檔中找到了兩個(gè) <span> 元素。因此,我們需要做額外的一步檢查,來(lái)確保最終結(jié)果的數(shù)組中不能包含重復(fù)的元素。 大多數(shù)top-down方式的選擇符引擎中都使用了若干確保元素唯一性的方法。
<div id="test"> <b>Hello</b>, I'm a ninja!</div> <div id="test2"></div> <script> (function () { var run = 0; this.unique = function (array) { var ret = []; run++; for (var i = 0, length = array.length; i < length; i++) { var elem = array[i]; if (elem.uniqueID !== run) { elem.uniqueID = run; ret.push(array[i]); } } return ret; }; })(); window.onload = function () { var divs = unique(document.getElementsByTagName("div")); assert(divs.length === 2, "No duplicates removed."); var body = unique([document.body, document.body]); assert(body.length === 1, "body duplicate removed."); }; </script>
其中的 unique() 方法給數(shù)組中的所有元素增加了一個(gè)額外的屬性,標(biāo)記它們是否被訪問(wèn)過(guò)。因此,當(dāng)所有元素都處理后,最終只剩下了不重復(fù)的元素。類(lèi)似本方法的其他算法可以在大多數(shù)CSS選擇符引擎中見(jiàn)到。
到目前為止,我們大致就構(gòu)造了一個(gè) 從上到下方式(top-down)的 CSS選擇符引擎。 現(xiàn)在,我們來(lái)看另外的一種方案。
從下到上的方式實(shí)現(xiàn)
如果你不用考慮唯一地確定元素,那么你可以以從下到上的方式(bottom-up)實(shí)現(xiàn)選擇符解析過(guò)程。它的流程跟從上到下的方式相反(復(fù)習(xí)那張解析過(guò)程的圖示)。例如,對(duì)于這樣的表達(dá)式 "div span",你需要首先找出所有 <span> 元素,然后對(duì)于每個(gè)候選元素,看看它們是否有一個(gè) <div> 的祖先元素。
這樣的方式并沒(méi)有 從上到下的方式 流行。盡管它能夠良好地處理簡(jiǎn)單的CSS選擇符表達(dá)式,但是在每個(gè)候選元素上對(duì)于祖先的遍歷就顯得太耗費(fèi)時(shí)間和資源了。
構(gòu)造從下到上方式的引擎很簡(jiǎn)單。首先找到CSS選擇符表達(dá)式中的最后一個(gè)部分,然后找出匹配的元素,接著按照一系列的過(guò)濾規(guī)則過(guò)濾掉不符合的元素。下面的代碼闡述了這一過(guò)程。
<div> <div> <span>Span</span> </div> </div> <script> window.onload = function () { function find(selector, root) { root = root || document; var parts = selector.split(" "), query = parts[parts.length - 1], rest = parts.slice(0, -1).join("").toUpperCase(), elems = root.getElementsByTagName(query), results = []; for (var i = 0; i < elems.length; i++) { if (rest) { var parent = elems[i].parentNode; while (parent && parent.nodeName != rest) { parent = parent.parentNode; } if (parent) { results.push(elems[i]); } } else { results.push(elems[i]); } } return results; } var divs = find("div"); assert(divs.length === 2, "Correct number of divs found."); var divs = find("div", document.body); assert(divs.length === 2, "Correct number of divs found in body."); var divs = find("body div"); assert(divs.length === 2, "Correct number of divs found in body."); var spans = find("div span"); assert(spans.length === 1, "No duplicate span was found."); }; </script>
注意,上述代碼只能處理一層祖先關(guān)系。如果需要處理多層祖先關(guān)系,那么當(dāng)前層的狀態(tài)則需要被記錄?紤]使用兩個(gè)數(shù)組:第一個(gè)數(shù)組記錄將要被返回的元素(其中的某些元素被設(shè)置成undefined,如果它們不能匹配表達(dá)式);第二個(gè)數(shù)組記錄當(dāng)前需要被測(cè)試的祖先節(jié)點(diǎn)。
就如之前所述,這一步額外的祖先關(guān)系驗(yàn)證會(huì)帶來(lái)更多的性能開(kāi)銷(xiāo)。但是按照從下到上的方式實(shí)現(xiàn)就不需要在結(jié)果集中取出重復(fù)元素的一步,因此它也算有一些優(yōu)勢(shì)。(因?yàn)榘凑諒南碌缴系姆绞皆谧铋_(kāi)始的時(shí)候每個(gè)元素就已經(jīng)是各自獨(dú)立不重復(fù)的了;而如果按照從上到下的方式,由于在遞歸時(shí)子樹(shù)可能相互重疊,所以那會(huì)包含重復(fù)的元素)
小結(jié)
JavaScript實(shí)現(xiàn)的CSS選擇符引擎是一個(gè)強(qiáng)大的工具。它能讓我們輕松地使用若干選擇符語(yǔ)法在頁(yè)面上尋找DOM元素。盡管在完全實(shí)現(xiàn)一個(gè)選擇符引擎時(shí)有非常多的細(xì)節(jié)需要考慮,這種情況正在大大的改善(得益于瀏覽器原生的方法)。
回顧一下本文討論的幾點(diǎn):
現(xiàn)代瀏覽器已經(jīng)開(kāi)始實(shí)現(xiàn)對(duì)于 W3C標(biāo)準(zhǔn)選擇符API的支持,但是依然有很長(zhǎng)一段路要走.
考慮到性能問(wèn)題,我們?nèi)匀挥斜匾獙?shí)現(xiàn)自己的選擇符引擎。
要?jiǎng)?chuàng)建一個(gè)選擇符引擎,我們可以:
利用 W3C標(biāo)準(zhǔn)的選擇符API
利用 XPath
為了最好的性能,使用純DOM操作方式
從上到下的方式非常流行,但是它需要一些清理工作:比如確保返回元素不重復(fù)。
從下到上的方式避免了那個(gè)清理工作,但是它會(huì)帶來(lái)更多的性能開(kāi)銷(xiāo)。
隨著瀏覽器逐漸支持W3C標(biāo)準(zhǔn)選擇符,顧慮引擎實(shí)現(xiàn)的細(xì)節(jié)或許會(huì)成為過(guò)去式了。但是對(duì)于許多開(kāi)發(fā)人員來(lái)說(shuō),那一天或許不能很快的到來(lái)。