更新:以前的答案有问题,留着鞭尸吧……
Sizzle的去重流程可以参考:
用compareDocumentPosition或其shim来判断节点关系,sort你的DOM Array
然后判断Array中前后两个节点是否全等,全等则将相关的编号push入duplicate数组
逆向遍历duplicate数组,把DOM Array里面的重复节点全部都splice掉
Sizzle.uniqueSort = function( results ) {
var elem,
duplicates = [],
j = 0,
i = 0;
results.sort( sortOrder );
while ( (elem = results[i++]) ) {
if ( elem === results[ i ] ) {
j = duplicates.push( i );
}
}
while ( j-- ) {
results.splice( duplicates[ j ], 1 );
}
return results;
};
源码见这里
如果compareDocumentPosition被浏览器支持,那么去重循环是单层的,反之则需要用到二重循环的内层来shim compareDocumentPosition,判断节点关系,见这里
最好的情况下,Sizzle方案的消耗为:sort大致为O(nlogn) + 一重去重循环O(n) + DOM总开销(未知)。
有时间了跑一个benchmark。
不过compareDocumentPosition作为DOM Level 3的内容,它的浏览器兼容,据我点开这里测试,是这样的结果:
ie6 ie7 ie8 ie9 chrome firefox
× × × √ √ √
在支持它的浏览器(IE9+)里,也支持querySelectorAll(IE8+,见这里),我们自己做selector的意义不大;
反之,不支持它的浏览器,我们需要自己实现compareDocumentPosition,这样就多了一层循环,Sizzle方案里的双层循环去重恐怕不可避免。
当然,如果我们用一个唯一性ID标识所有的DOM节点,倒是可以单层循环去重。只不过这个方案的开销可不比Sizzle的方案小,还需要跟踪所有的DOM新增节点。
下面是尸体:
回到需求。
按照选择符描述去正向匹配是有这种过滤、父子层级的问题。这就是为什么CSS选择器的浏览器实现,以及高效的sizzle走了相反的路子:反向匹配。
这样的话,对于选择器div span,我们先找到所有的span,然后判断它的祖先层级是不是div。
你看,需要做去重吗?
想了一下,在这个问题上,读取的顺序不是太重要,对于这样的文档结构还是有重复的问题: