通常的去重是针对简单的数组,可以使用一个对象,将数组值作为对象属性,然后判断该对象的索引是否有定义。
var distinctArr = function(arr) {
var i = 0,
j = arr.length,
cacheObj = {}, resArr = [];
for (; i < j; i++) {
if (!cacheObj[arr[i]]) {
cacheObj[arr[i]] = 1;
resArr.push(arr[i]);
}
}
return resArr;
}
但是,怎么把DOM集合去重呢(把DOM集合 makeArray后)。
以上的方法针对DOM集合是无效的。
去重就是基于这种情形:
p>p>span
当我想用类似$('p span')
的方式来获取span
时(个人js原生实现,非jquery),我是先获取p
,然后在得到的前一个结果集中寻找下一个符合条件的span
,依此类推。
如此方法,我获取p
,肯定得到的数组长度为2了。再寻找span
时,就出问题了,按照这种方法寻找,肯定会找到两个相同的span
了。
于是我就想得到所有结果后,进行一次过滤操作。
当然,还有一种方法好像在处理过程中用contains
和compareDocumentPosition
来判断节点关系的,这样没必要进行去重操作了。
单层循环能搞定么,两层循环判断太慢了。
更新:以前的答案有问题,留着鞭尸吧……
Sizzle的去重流程可以参考:
compareDocumentPosition
或其shim来判断节点关系,sort你的DOM Array源码见这里
如果compareDocumentPosition被浏览器支持,那么去重循环是单层的,反之则需要用到二重循环的内层来shim compareDocumentPosition,判断节点关系,见这里
最好的情况下,Sizzle方案的消耗为:sort大致为
O(nlogn)
+ 一重去重循环O(n)
+ DOM总开销(未知)。有时间了跑一个benchmark。
不过compareDocumentPosition作为DOM Level 3的内容,它的浏览器兼容,据我点开这里测试,是这样的结果:
在支持它的浏览器(IE9+)里,也支持querySelectorAll(IE8+,见这里),我们自己做selector的意义不大;
反之,不支持它的浏览器,我们需要自己实现compareDocumentPosition,这样就多了一层循环,Sizzle方案里的双层循环去重恐怕不可避免。
当然,如果我们用一个唯一性ID标识所有的DOM节点,倒是可以单层循环去重。只不过这个方案的开销可不比Sizzle的方案小,还需要跟踪所有的DOM新增节点。
下面是尸体:
回到需求。按照选择符描述去正向匹配是有这种过滤、父子层级的问题。这就是为什么CSS选择器的浏览器实现,以及高效的sizzle走了相反的路子:反向匹配。这样的话,对于选择器
p span
,我们先找到所有的span,然后判断它的祖先层级是不是p。你看,需要做去重吗?想了一下,在这个问题上,读取的顺序不是太重要,对于这样的文档结构还是有重复的问题:
用hash表