技术开发 频道

Jquery技巧讲解:简化closest函数

  【IT168 技术】在实现delegate方法中,有一个很重要的辅助函数叫closest,虽然现在它归类为遍历节点这个模块中。这个函数实现得非常复杂,洋洋洒洒近50行,完全不符合极限编程的规矩。

closest: function( selectors, context ) {

  var ret
= [], i, l, cur = this[0];

  
// Array

  
if ( jQuery.isArray( selectors ) ) {//这分支的过滤逻辑基本与下面的一致

  var match, selector,

  matches
= {},

  level
= 1;

  
if ( cur && selectors.length ) {

  
for ( i = 0, l = selectors.length; i < l; i++ ) {

  selector
= selectors[i];

  
if ( !matches[ selector ] ) {

  matches[ selector ]
= POS.test( selector ) ?

  jQuery( selector, context || this.context ) :

  selector;

  }

  }

  
while ( cur && cur.ownerDocument && cur !== context ) {

  
for ( selector in matches ) {

  match
= matches[ selector ];//这里频繁创建新的jQuery对象与使用is这样复杂的方法,我不觉得其高效到哪里去

  
if ( match.jquery ? match.index( cur ) > -1 : jQuery( cur ).is( match ) ) {

  ret.push({ selector: selector, elem: cur, level: level });

  }

  }

  cur
= cur.parentNode;

  level
++;

  }

  }

  return ret;

  }

  
// String

  var pos
= POS.test( selectors ) || typeof selectors !== "string" ?
  jQuery( selectors, context || this.context ) :

  0;

  
for ( i = 0, l = this.length; i < l; i++ ) {

  cur
= this[i];

  
while ( cur ) {

      if ( pos ? pos.index(cur) > -1 : jQuery.find.matchesSelector(cur, selectors) ) {

  ret.push( cur );

  break;

  }
else {

  cur
= cur.parentNode;

  
if ( !cur || !cur.ownerDocument || cur === context || cur.nodeType === 11 ) {

  break;

  }

  }

  }

  }

  ret
= ret.length > 1 ? jQuery.unique( ret ) : ret;

  return this.pushStack( ret,
"closest", selectors );

  },

恰逢我也想造个轮子,便去研究它一翻,发现其第一个可以是字符串,元素节点或jQuery对象,还有一个可选参数,上下文。看源码前几句,发现有个分支是判断是否是Array,估计是供内部调用的优化代码,可以无视之。于是其方法简化为:

closest: function( selectors, context ) {

  var ret
= [], i, l, cur = this[0];

  
// 如果字符串包含位置伪类或者是个元素节点,则封装为一个jQuery对象,否则为0(即false的简写,用于快速跳过分支)

  var pos
= POS.test( selectors ) || typeof selectors !== "string" ?

  jQuery( selectors, context || this.context ) :

  
0;

  
//遍历原jQuery对象的节点

  
for ( i = 0, l = this.length; i < l; i++ ) {

  cur
= this[i];

  
while ( cur ) {

  
//如果是jQuery对象,则判定其是否包含当前节点,否则使用matchesSelector方法判定这个节点是否匹配给定的表达式selectors

  
if ( pos ? pos.index(cur) > -1 : jQuery.find.matchesSelector(cur, selectors) ) {

 

  //是则放入选择器中

  ret.push( cur );

  break;

  }
else {

  
// 否则把当前节点变为其父节点

  cur
= cur.parentNode;

  
if ( !cur || !cur.ownerDocument || cur === context || cur.nodeType === 11 ) {

  break;

  }

  }

  }

  }

  
//如果大于1,进行唯一化操作

  ret
= ret.length > 1 ? jQuery.unique( ret ) : ret;

  
//将节点集合重新包装成一个新jQuery对象返回

  return this.pushStack( ret,
"closest", selectors );

  },

由于本人很反感位置伪类,认为其违反选择器的法则之一(由关系选择器隔开的各选择器分组内部,它们的位置是随意的),因此有关位置伪类的逻辑我也去掉了。

closest: function( selectors ) {

  var ret
= [], i, l, cur = this[0];

  
// 如果字符串包含位置伪类或者是个元素节点,则封装为一个jQuery对象,否则为0(即false的简写,用于快速跳过分支)

  var node
= selectors.nodeType ? selectors :false;

  var nodes
= [].slice.call(this);//将jQuery对象转换为纯数组

  
//遍历原jQuery对象的节点

  
for ( i = 0, l = this.length; i < l; i++ ) {

  cur
= this[i];

  
while ( cur ) {

  
//如果是jQuery对象,则判定其是否包含当前节点,否则使用matchesSelector方法判定这个节点是否匹配给定的表达式selectors

  
if ( obj ? nodes.indexOf(node) > -1 : jQuery.find.matchesSelector(cur, selectors) ) {

  
//indexOf方法在某些浏览器需要自行实现

  
//是则放入选择器中

  ret.push( cur );

  break;

  //如果大于1,进行唯一化操作

  ret
= ret.length > 1 ? jQuery.unique( ret ) : ret;

  
//将节点集合重新包装成一个新jQuery对象返回

  return $(ret);
//本人觉得pushStack真是个邪恶的方法,让菜鸟不籽有链下去的欲望,殊不知这是维护的大敌

  },

 

  } else {

  
// 否则把当前节点变为其父节点

  cur
= cur.parentNode;

  
//如果没有父节点(说明其还没有插入DOM树),或不是元素节点,或是文档碎片(说明其刚被移出DOM树)

  
if ( !cur || !cur.ownerDocument || cur.nodeType === 11 ) {

  break;

  }

  }

  }

  }

  下面是我的实现:

closest: function( expr ) {

  
// 如果字符串包含位置伪类或者是个元素节点,则封装为一个dom对象,否则为0(即false的简写,用于快速跳过分支)

  var node
= expr.nodeType ? expr : 0, nodes = dom.slice(this);//将它转换为纯数组

  
//遍历原dom对象的节点

  
for (var i = 0, ret = [], cur; cur = this[i++];) {//由于肯定里面都是节点,因此可以使用这种循环

  
while (cur && cur.nodeType === 1 ) {

  
//如果是dom对象,则判定其是否包含当前节点,否则使用matchesSelector方法判定这个节点是否匹配给定的表达式expr

  
if ( node ? nodes.indexOf(node) > -1 : matchElement( cur, expr ) ){

  
//indexOf方法在某些浏览器需要自行实现

  
//是则放入选择器中

  ret.push( cur );

  break;

  }
else {

  
// 否则把当前节点变为其父节点

  cur
= cur.parentNode;

  }

  }

  }

  
//如果大于1,进行唯一化操作

  ret
= ret.length > 1 ? dom.unique( ret ) : ret;

  
//将节点集合重新包装成一个新dom对象返回

  return this.labor(ret);

  },
0
相关文章