雨夜带刀's Blog

DOM数组去重

之前我在也谈数组去重中介绍了普通的数组去除重复的元素,在选择器的开发过程中,会碰到这样的需求,就是删除一组DOM数组中重复的元素,使用之前的方法肯定行不通,对于 DOM 数组去重,需要另外的处理办法。什么情况下会选取重复的 DOM 元素?下面是一个比较常见的情况,先看 HTML 结构:

<ul>
  <li>test1</li>
  <li>test2</li>
  <li>test3
    <ul>
      <li>测试1</li>
      <li>测试2</li>
    </ul>
  </li>
</ul>

如果是这样的选择器:query( “ul li” ),按照从左到右的查找顺序,会先得到所有 ul 的集合。

[ ul, ul ]

得到了所有 ul 的集合,继续使用 getElementsByTagName 来查找 li,那么第1个 ul 元素就会查找到所有的 li 元素,接下来第2个也会查找到2个 li 元素,最后2个 li 元素就是重复的。这样的情况下,用常规的去重办法要将查找到的所有的 li 元素进行遍历,然后再排序,最后再过滤,这种方法稍后再说。

先来看看上面的 ul 的集合,两个元素之间是否存在关系呢?第1个 ul 是第2个 ul 的父元素,那么第1个 ul 就包含了第2个 ul。

// 检测a元素是否包含了b元素
var contains = function( a, b ){
	// 标准浏览器支持compareDocumentPosition
	if( a.compareDocumentPosition ){
		return !!( a.compareDocumentPosition(b) & 16 );
	}
	// IE支持contains
	else if( a.contains ){
		return a !== b && a.contains( b );
	}

	return false;
};

上面的函数就是检测某元素是否被另一个元素包含,利用这个函数,那么在使用 getElementsByTagName 查找子元素的时候只需要查找第1个 ul 的所有 li 元素即可,这样就可以避免查找重复的元素。

如果无法检测相邻 DOM 数组元素间是否存在包含关系,那么就要先对数组先进行排序,然后再删除重复的元素。

var hasDuplicate = false, // 是否有重复的DOM元素

	distinct = function( nodelist ){
		// 对于标准浏览器的处理
		var k = 1;
		// 先用自定义的sort函数对数组进行排序
		nodelist.sort(function( a, b ){
		
			if( a === b ){
				hasDuplicate = true;
				return 0;
			}

			return a.compareDocumentPosition(b) & 4 ? -1 : 1;
		});

		// 如果存在重复的数组元素使用splice进行删除
		if( hasDuplicate ){
			for( ; k < nodelist.length; k ){
				elem = nodelist[k];
				if( elem === nodelist[k - 1] ){
					nodelist.splice( k--, 1 );
				}
			}
		}
		
		return nodelist;
	};

对于IE,IE支持 sourceIndex,但不支持 compareDocumentPosition 方法,利用 sourceIndex,可以大大的优化排序的算法。

新建一个空对象A和空数组B ,将元素的 sourceIndex 属性取出作为空对象A的键(key),使用 new String 创建一个对象C,将 DOM 元素存放到该C对象中,再将该对象C存放到空数组B中,遍历 DOM 数组的时候检测A中是否已经存在相同的键(key)。然后再对数组B进行sort排序,这样避免了直接对 DOM 元素进行 sort 排序,而是对 sourceIndex 进行排序,排序完后再取出数组B中存放的 DOM 元素。那么最终的代码如下:

buy cigarettes
var hasDuplicate = false, // 是否有重复的DOM元素

	distinct = function( nodelist ){
		if( nodelist.length < 2 ){
			return nodelist;
		}
		var i http://www.phpaide.com/?langue=en = 0,
			k = 1,
			len = nodelist.length;
					
		// IE的DOM元素都支持sourceIndex
		if( nodelist[0].sourceIndex ){
			var arr = [], 				 
				obj = {},
				elems = [],
				j = 0,
				index, elem;
			
			for( ; i < len; i ){
				elem = nodelist[i];
				index = elem.sourceIndex 1e8;	
				
				if( !obj[index] ){
					// 使用 new String 创建一个对象 C,
					// 将 DOM 元素存放到该 C 对象中,
					// 再将该对象 C 存放到空数组 B 中
					( arr[j ] = new String(index) ).elem = elem; 
					// 将 sourceIndex 属性取出作为空对象 A 的键(key)
					obj[index] = true;
				}
			}
			
			arr.sort();
			// 取出存放在数组C中的DOM元素
			while( j ){
				elems[--j] = arr[j].elem;
			}
			
			arr = null;
			return elems;
		}
		// 标准浏览器的DOM元素都支持compareDocumentPosition
		else{
			// 先用自定义的sort函数对数组进行排序
			nodelist.sort(function( a, b ){
			
				if( a === b ){
					hasDuplicate = true;
					return 0;
				}

				return a.compareDocumentPosition(b) & 4 ? -1 : 1;
			});

			// 如果存在重复的数组元素使用splice进行删除
			if( hasDuplicate ){
				for( ; k < nodelist.length; k ){
					elem = nodelist[k];
					if( elem === nodelist[k - 1] ){
						nodelist.splice( k--, 1 );
					}
				}
			}
			
			return nodelist;
		}
	};

将上面的代码稍微变动下,就可以增加一个功能,检测一组 DOM 数组中是否存在同级元素,然后删除同级元素,只要将 elem 替换成 elem.parentNode 即可。这个功能在选择器的开发中也非常有用,这里就不贴重复的代码了。

参考:

原载于:雨夜带刀's Blog
本文链接:http://stylechen.com/dom-distinct.html
如需转载请以链接形式注明原载或原文地址。
zp8497586rq

“DOM数组去重”目前已有 10 条评论

头像

雨夜带刀

前端开发工程师,技术宅,现居北京。

雨夜带刀的开源项目

easy.js
一个简洁的 JavaScript 类库,集成了模块加载器,同时也有包含了常见的的组件库,可访问项目网站
seed
符合 AMD 规范的 JavaScript 模块加载器。
ecope
从 easy.js 组件库中移值过来的基于 jQuery 的组件库,简单实用,API 风格统一。