随着css selector engine在越来越多的javascript库中实现,进而影响了浏览器开发商,在现代(除了ie)的浏览器中都实现了css selector api : querySelector,querySelectorAll ,利用原生的 render engine ,速度得到了极大的提高,最终反馈到新的javascript库中,在进行选择器筛选时都要首先进行能力检测(feature test),适时地选择原生实现。
这里说明一下使用原生 querySelectorAll? 会遇到的问题以及解决思路:
node.querySelectorAll (selector) ,根据规范定义:
The querySelectorAll() method on the NodeSelector interface must, when invoked, return a NodeList containing all of the matching Element nodes within the node’s subtrees, in document order. If there are no such nodes, the method must return an empty NodeList.
简单的说就是在节点的子树集合中查找符合css选择串的节点集合。
测试html结构:
?
<div id="test"> <b id="wrong"></b> <div id="testInner"> <b id="ok"> </b> </div> </div> <div> </div>?
?
?
而下面例子就有点奇怪:
?
?
document.getElementById("test").querySelectorAll("div b"); // 2 : wrong , ok document.getElementById("test").querySelectorAll("div"); // 1 : testInner?
若只在节点的子树集合中则与第一句代码矛盾,若在包括节点的子树中查找则与第二句代码矛盾,一般的来说我们期望第二句代码的结果,当限定context为一个节点时,一个css 选择器期望是从context的子节点开始匹配而不是从context节点自身开始。
以及更加诡异的
?
<div><p id="foo"><span></span></p></div> var foo = document.getElementById("foo"); // should return nothing // will return the SPAN (booo!) alert(foo.querySelectorAll('div span').length)?
?
综合判断似乎浏览器的实现是:
?
element-rooted queries are handled by "finding all the elements that match the given selector -- rooted in the document -- then filtering by the ones that have the specified element as an ancestor."
?
先在文档中找出所有符合选择器的元素,然后再根据是否其祖先结点是context来进行筛选。
库的解决
对于最先实现css selector engine 的javascript库,选择的是更符合人们期望的:给定的css selector从context 的子节点开始匹配,而现在又要尽可能的利用原生的实现,对于这个问题为了兼容性考虑,要和以往的版本保持一致,也就决定了不能简单的调用 node.querySelectorAll
1.jquery
jquery 对于 context 为 document 即没有 context 的情况,调用 document.querySelectAll ,而对于context 为节点的情况,其实转而调用的是纯dom实现的选择器引擎:
?
/* 1.4.2 line : 3528 判断只能是document才能使用原生 context === document ,只使用 document.querySelectorAll if ( !seed && context.nodeType === 9 && !isXML(context) ) { try { return makeArray( context.querySelectorAll(query), extra ); } catch(e){} } line:2746 oldsizzel 判断必须是包含在context节点中 contains(context, checkSet[i])) ) */ $("#test").find("div b"); // 1 : ok?
?
2.yui3
?
yui3 则是采用一种比较聪明的做法,利用 id 选择器强制限制context,例如
document.getElementById("test").querySelectorAll("div b") 则被强制转换为
document.getElementById("test").querySelectorAll("#test div b")
?
也即:document. querySelectorAll("#test div b")
?
这样强制下层选择器从子节点开始匹配:使得 context中的querySelectorAll 变得无关紧要
?
YUI({filter:"DEBUG"}).use("node",function(Y){ /*line 2597 enforce for element scoping if (node.tagName) { node.id = node.id || Y.guid(); prefix = '[id="' + node.id + '"] '; } 然后 document.getElementById("test").querySelectorAll("#test div b"); */ Y.one("#test").all("div b"); // 1:ok });
?
3.Extjs
extjs 我认为采用的是一种比较落后的做法,虽然采用了创新的编译函数 做法,但其根本没有采用浏览器的原生实现,不过也自然免除了这种特殊情况的考虑:
?
Ext.get("test").select("div b") //1:ok
?
PS: john resig 很早就注意到了这个问题 ,并在 Secrets of the JavaScript Ninja ? 11章正式说明了这个情况,虽然解释比较奇怪:
When performing an element-rooted query (calling querySelector or
querySelectorAll relative to an element) the selector only checks to see if the final portion of the
selector is contained within the element.
?
书中提出了同yui3类似的方法,且不具有侵入性:
?
(function () { var count = 1; this.rootedQuerySelectorAll = function (elem, query) { var oldID = elem.id; //解决奇怪的浏览器api问题 elem.id = "rooted" + (count++); //CSS Selector Engine try { return elem.querySelectorAll("#" + elem.id + " " + query); } catch(e) { throw e; } finally { //不具有侵入性 elem.id = oldID; } }; })();?
?
?
?