jQuery.unique()的实现方式

[align=center][size=large][b]jQuery.unique()的实现方式[/b][/size][/align]
[align=center][size=medium][b]jQuery 中的 unique()[/b][/size][/align]
  jQuery中 的 unique() 函数实现了对 DOM ELement按出现位置进行排序并去除重复元素的功能。使用方法如下:

<html>
<head></head>
<body onload="test()">

<div id="div_1">
<div id="div_2" />
</div>
<div id="div_3" />

<script type='text/javascript' src='jquery.js'></script>

<script type='text/javascript'>
function test() {
var divs = [
document.getElementById('div_3'),
document.getElementById('div_1'),
document.getElementById('div_2'),
document.getElementById('div_2'),
document.getElementById('div_1')
];
var html = show('before uinque', divs);

$.unique(divs);
html += show('after uinque', divs);

document.write(html);
}

function show(name, divs) {
var html = '';
for (var i = 0; i < divs.length; ++i) {
html += divs[i].getAttribute('id') + '<br />';
}
return name + ':<br />' + html + '<br />';
}
</script>

</body>
</html>

  显示结果为:

before uinque:
div_3
div_1
div_2
div_2
div_1

after uinque:
div_1
div_2
div_3

  从函数名来看,应该主要是用于去除重复元素的,为什么同时又先对 DOM Element 进行排序?为了解释这个问题,先让我们试一下不事先进行排序的情况下需要如何去除重复。

[align=center][size=medium][b]不排序的去除重复元素的实现[/b][/size][/align]
  这里给出两种实现:

$ = function() {
return {
unique: function(elems) {
for (var i = 0; i < elems.length; ++i) {
for (var j = i + 1; j < elems.length; ++j) {
if (elems[i] === elems[j]) {
elems.splice(i--, 1);
break;
}
}
}

return elems;
},

unique2: function(elemsWithId) {
var obj = {};
for (var i = 0; i < elemsWithId.length; ++i) {
var elem = elemsWithId[i];
obj[elem.getAttribute('id')] = elem;
}

elemsWithId.length = 0;
for (var id in obj) {
elemsWithId.push(obj[id])
}

return elemsWithId;
}
}
}();

  实现一使用了两重循环,算法复杂度为O(n^2);实现思路比较直观,即遍历数组,看每个元素是否与后面的元素重复,有重复则移除;但当DOM Element数量较多时性能较差,而jQuery中对大量元素进行去除重复的操作是很普遍的。
  实现二将 Object 当做 HashMap/HashSet 来使用,算法复杂度为O(n);遗憾的是JavaScript中无法直接用 DOM ELement 作为 Object 的 key ,因此只能将 id 作为 key ,然而并非所有的 DOM Element 都是有 id 的,所以这种方法并不通用。而自己实现一个高性能的 HashSet(还需要自己动手计算 DOM Element 的 Hash Code ),工作量又比较大。
  
  我们知道,基于比较的排序算法最多可以将算法复杂度降到O(nlgn),(比如结合使用快速排序和插入排序),之后遍历数组时只要比较相邻元素就可以了:

unique3: function(sortedElems) {
for ( var i = 1; i < sortedElems.length; i++ ) {
if ( sortedElems[i] === sortedElems[ i - 1 ] ) {
sortedElems.splice( i--, 1 );
}
}

return sortedElems;
}

  JavaScript中又有内置的排序算法。因此,在JavaScript中,先排序后去除重复是较好的做法。

[align=center][size=medium][b]先排序后去除重复[/b][/size][/align]
  先排序后去除重复的实现方式如下:

$ = function() {
var sort = function(a, b){
var aParents = getParents(a),
bParents = getParents(b),
al = aParents.length,
bl = bParents.length,
i, ap, bp;

for (i = 0; i < al && i < bl; i++) {
ap = aParents[i];
bp = bParents[i];
if (ap !== bp) {
return siblingCheck(ap, bp);
}
}

return i === al ?
siblingCheck(a, bp, -1) :
siblingCheck(ap, b, 1);
};

var getParents = function(elem) {
var ret = [], cur;
for (cur = elem; cur; cur = cur.parentNode) {
ret.unshift(cur);
}

return ret;
}

var siblingCheck = function(a, b, ret) {
if (a === b) { return ret; }

var cur = a;
while (cur && (cur = cur.nextSibling)) {
if (cur === b) {
return -1;
}
}
return 1;
};

return {
unique : function(elems) {
elems.sort(sort);
for ( var i = 1; i < elems.length; i++ ) {
if ( elems[i] === elems[ i - 1 ] ) {
elems.splice( i--, 1 );
}
}

return elems;
}
}
}();

  这里使用了 Array 的 sort(...) 方法,并传入自定义的排序函数(sort函数)。
  sort函数的做法是获取两个被比较元素的所有“直系祖宗”,从而确定了两个元素在 DOM 树中的位置;一般来说,两个元素有共同的根,那么就从根元素开始依次向下遍历,直到“分叉点”,再对分叉点的元素进行比较。如果直到遍历结束,仍未能达到分叉点(如元素a先遍历完,那么 i 就与 al 相等了),则直接将 a 与 b 当前遍历到的“祖宗”进行比较。

[align=center][size=medium][b]排序时检查重复[/b][/size][/align]
  接下来,我们针对这样的情况进行优化:假设元素中不存在重复,那么我们就可以不执行后面的遍历并去除重复的操作了。实现如下:

$ = function() {
var hasDuplicate = true;
[0, 0].sort(function() {
hasDuplicate = false;
return 0;
});

var sort = function(a, b){
if (a === b) {
hasDuplicate = true;
return 0;
}

// Other Codes ...
};

var getParents = function(elem) {
// Other Codes ...
}

var siblingCheck = function(a, b, ret) {
// Other Codes ...
};

return {
unique : function(elems) {
elems.sort(sort);
if (hasDuplicate) {
for ( var i = 1; i < elems.length; i++ ) {
if ( elems[i] === elems[ i - 1 ] ) {
elems.splice( i--, 1 );
}
}
}

return elems;
}
}
}();

  一个特殊的例外是某些浏览器可能进行了特殊的优化,那么在元素相等时可能就没有调用我们的排序函数了;这种情况下,排序时检查重复的方案就不可行。因此,如果浏览器在元素相等的情况下会调用我们的排序函数,那么就将 hasDuplicate 置为 false,并在排序过程中检查重复;否则,无论如何都视为存在重复,仍然进行遍历去除重复的操作。

[align=center][size=medium][b]使用compareDocumentPosition()进行优化[/b][/size][/align]
  事实上,除 IE 之外浏览器都有内置的 compareDocumentPosition() 方法,用于比较两个 DOM Element 的位置,因此我们可以引入 compareDocumentPosition() 进行优化:

if (document.documentElement.compareDocumentPosition) {
var sort = function(a, b) {
if (a === b) {
hasDuplicate = true;
return 0;
}

if (!a.compareDocumentPosition || !b.compareDocumentPosition) {
return a.compareDocumentPosition ? -1 : 1;
}

return a.compareDocumentPosition(b) & 4 ? -1 : 1;
}
} else {
var sort = function(a, b) {
// Original implemention ...
}
}

  a.compareDocumentPosition(b) & 4 表示取返回值的二进制表示的倒数第三位;compareDocumentPosition() 的返回值的每个二进制位表示不同的含义,其中倒数第三位表示元素 a 是否在元素 b 的 “前面”,这正是我们需要的。

[align=center][size=medium][b]最后的优化[/b][/size][/align]
  最后,在sort的原始实现前插入一些代码,使得该函数有机会提前退出:

var sort = function(a, b){
var aParents = getParents(a),
bParents = getParents(b),
al = aParents.length,
bl = bParents.length,
ap = a.parentNode,
bp = b.parentNode,
i;

if (a === b) {
hasDuplicate = true;
return 0;

// If the nodes are siblings (or identical) we can do a quick check
} else if (ap === bp) {
return siblingCheck( a, b );

// If no parents were found then the nodes are disconnected
} else if (!ap) {
return -1;

} else if (!bp) {
return 1;
}

for (i = 0; i < al && i < bl; i++) {
ap = aParents[i];
bp = bParents[i];
if (ap !== bp) {
return siblingCheck(ap, bp);
}
}

return i === al ?
siblingCheck(a, bp, -1) :
siblingCheck(ap, b, 1);
};

  以上就是 jQuery.unique() 的实现过程了。jQuery 的 unique() 在去除重复前先进行了排序,并使用了多种优化手段,实现了性能较好并且比较通用的去除重复的 DOM Element 的功能。
  附件是本文中用到的一些代码。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值