jQuery源码分析之closest,has,addBack,addSelf,add方法

请提前阅读正则表达式,同时请提前阅读jQuery(selector,context)相关知识并理解这种调用方式是获取context下的子元素!

closest源码分析(用于从当前匹配元素开始,逐级向上级选取符合指定表达式的第一个元素,并以jQuery对象的形式返回):

closest: function( selectors, context ) {
var cur,
i = 0,
l = this.length,
matched = [],
var rneedsContext=^[\x20\t\r\n\f]*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\([\x20\t\r\n\f]*((?:-\d)?\d*)[\x20\t\r\n\f]*\)|)(?=[^-]|$)/i;
pos = rneedsContext.test( selectors ) || typeof selectors !== "string" ?jQuery( selectors, context || this.context ) :0;
//如果传入的selector不能通过rneedsContext,如"span"那么pos就是0,接下来调用jQuery.find.matchesSelector方法
 //对调用对象每一个DOM元素进行循环
for ( ; i < l; i++ ) {
//获取每一个DOM对象的父元素
for ( cur = this[i]; cur && cur !== context; cur = cur.parentNode ) {
// Always skip document fragments
if ( cur.nodeType < 11 && (pos ?pos.index(cur) > -1 :
// Don't pass non-elements to Sizzle
//跳过不是Element类型的元素
cur.nodeType === 1 &&
//调用jQuery.find.matchesSelector(cur, selectors)
//用于检查某个元素node是否匹配选择器表达式expr
jQuery.find.matchesSelector(cur, selectors)) ) {
      //跳过documentFragment对象,满足条件添加到结果集合中间
matched.push( cur );
break;
 }
   }
    }
return this.pushStack( matched.length > 1 ? jQuery.unique( matched ) : matched );
      }

HTML部分

 <p id="n1">
    <span id="n2">
        <span id="n3">A</span>
    </span>
    <label id="n4">B</label>
    <span id="n5" class="active">
        <span id="n6" class="start active">C</span>        
    </span>
    <strong id="n7" class="active">D</strong>
    <span id="n8" class="active">E</span>
</p>
<p id="n9" class="active">
    <span id="n10"></span>
    <label id="n11"></label>
    <span id="n12">
        <span id="n13" class="start">F</span>
    </span>
</p>

问题1:如果传入的第一个参数是符合rneedsContext,第二个参数是结束节点DOM?

解答:这时候我们通过这两个选择器构建出来一个选择结果pos。接着就遍历调用对象,如果有一个调用对象的某一个父元素在这个集合里面,我们就获取这个父元素,然后跳出循环

var $starts = $("span.start");
var $doms = $starts.closest( ":even", document.getElementById("n9") );
//这时候通过:even选择器和后面的DOM构建一个选择结果对象,然后遍历$starts中的
//DOM对象,只要有一个DOM对象的父元素(或者自身)在该集合里面,就获取该元素,并且
//跳出循环!
问题2:如果传入第一个参数是DOM,第二个参数也是DOM,那么这时候也会通过jQuery(selector,context)来选择,和上面的顺序是一样的?

var $doms=jQuery(document.getElementById("n10"), document.getElementById("n9"));
console.log($doms);
这时候就会选中n10对应的DOM元素
var $starts = $("span.start");
//通过jQuery(selector,context)选择的结果是n10!
var $doms = $starts.closest(document.getElementById("n10"), document.getElementById("n9") );
//第二步:从$starts自身开始不断往上寻找父元素,如果父元素在后面的集合里面,那么就返回该父元素!
console.log($doms);
这时候不会选中任何元素,因为n10不是starts中元素的父元素!
var $starts = $("span.start");
//通过jQuery(selector,context)选择的结果是n10!
var $doms = $starts.closest(document.getElementById("n12"), document.getElementById("n9") );
//第二步:从$starts自身开始不断往上寻找父元素,如果父元素在后面的集合里面,那么就返回该父元素!
console.log($doms);
这时候就能选中n12,因为在查找$starts中的父元素时候,发现n12是$starts中元素的父元素!
问题3:是否closest方法是返回的是一个集合?

var $starts=$("span.start");
var doms=$starts.closest(".active");
console.log(doms);
因为里面是一个双重for循环,里面的break 只会跳出内存的循环,所以这里会搜寻所有的$starts中的DOM的父元素!

总之:只有当第一个参数传递rneedContext或者DOM的时候需要特殊处理,也就是重新选择出结果,其它的情况都是很好理解的!在特殊情况下,第二个参数是context,而在一般情况下第二个参数是结束元素!

总结:

(1)调用方式就是jQueryObject.closest( expr [, context ] )其中第二个参数表示搜索结束的元素,也就是让调用对象每一个DOM不断向上搜索一直到context结束!

(2)如果第一个context是满足even|odd|eq|gt|lt|nth|first|last正则表达式或者第一个参数不是string,那么就把两者结合起来构建一个jQuery选择器,然后只要该父元素在这个结合起来的jQuery选择器中那么就满足结果!(这时候的第二个参数仍然是结束的标记!)

(3)记住这里不断取父元素是从调用对象开始的,而不是从调用对象的第一级父元素开始的!

(4)上面jQuery( selectors, context || this.context )这一段代码很关键,也就是如果你按照这种方式调用$starts.closest( ":even");那么context是不存在的,那么这时候就用用this.context也就是调用对象的context,也就是$starts的context,但是如果没有为$starts指定context,如这样调用var $starts=$("#starts")那么context就成了document对象了,于是这时候会把所有的符合:even表达式的结果选择出来,如HTML,TITILE,SCRIPT,BODY等!修改这个demo的最后一个例子你就会明白。

:even选择器的一个例子:(我们测试有context的因素)

HTML部分:
<div id="parent">
 <div class="child">
  child1
</div>
<div class="child">
child2
</div>
</div>
<div id="parent1">
 <div class="child">
  child1
</div>
<div class="child">
child2
</div>
</div>

JS部分:

<span style="font-size:10px;">//第二个context传入了DOM数组,这时候打印4
alert($(".child",[document.getElementById("#parent"),document.getElementById("#parent1")]).length)
//这时候只会选择id是parent下面的class为child的元素,打印2。现在弄懂了jQuery(selector,context)的第二个context参数了把.
alert($(".child",document.getElementById("parent")).length)</span>

jQuery的:has(selector)选择器用于匹配所有包含selector元素的元素,将其封装为jQuery对象并返回。这里你应该明白:jQuery(selector,jQuery)这种选择方式第二个参数可以是DOM数组,也可以是jQuery对象!

源码如下:

//调用方式:$("#n3").has(".foo")
has: function( target ) {
var i,
//target=".foo",查找对象是$("#n3")指定的上下文,这是对查找的选择器的上下文进行了限定!
//调用方式:jQuery( selector, [ context ] )
targets = jQuery( target, this ),  
//查找的结果的个数
len = targets.length;
 //对查找的结果进行筛选,this指的是$("#n3")
return this.filter(function() {
for ( i = 0; i < len; i++ ) {
//contains调用方式jQuery.contains( container, contained )
////jQuery,.contains底层调用的方法来自于JS的contains方法和compareDocumentPosition方法
//这里面的this指的是$("#n3")的选择结果的每一个元素,是DOM元素$("#n3")[i]看他是否包括通过参数选择出来的jQuery对象构建的每一个DOM对象
//这里指的是target[i]对每一个$("#n3")[i]都要循环len次,只要他包含targets[len]中的任何一个元素那么返回true
if ( jQuery.contains( this, targets[i] ) ) {
return true;
  }
    }
    });
}

之所有用filter方法,是因为返回的是调用对象的一个子集,所以只要包含selector的元素都会在结果集合中!

总结:

(1)要注意这里的argets=jQuery(target,this)是最重要的一句,也就是上下文是调用对象,寻找的是调用对象下(也就是子元素)的符合特定的选择器结果!

(2)底层调用的是filter方法,也就是直接返回的符合特定选择器的调用对象!也就是返回的结果是调用对象的一个子集!

addBack()函数用于将之前匹配的元素加入到当前匹配的元素中,并以新的jQuery对象的形式返回。

addBack(在jQuery.1.11.1中和addSelf一样)源码如下:

//addBack函数jQuery.fn.andSelf = jQuery.fn.addBack;
//调用方式:jQueryObject.addBack( [ selector ] )
addBack: function( selector ) {
return this.add( selector == null ?
//如果调用是没有传入参数,那么就直接加入prevObject,该对象用于jQuery的end方法
 //如果传入的选择器,那么就对prevObject进行再次筛选。
//要记住:prevObject用于保存上一次的状态,在jQuery中就是end方法的作用
this.prevObject : this.prevObject.filter(selector)
);
add() 函数用于 向当前匹配元素中添加符合指定表达式的元素, 并以jQuery对象的形式返回。
add方法源码如下:

//调用方式:var $elements1 = $("p").add("label");
add: function( selector, context ) {
return this.pushStack(
jQuery.unique(
   //把通过selector获取到的jQuery对象添加到当前选择器中,然后去重就可以了
    //在pushStack中通过prevObject保存add调用之前的状态
jQuery.merge( this.get(), jQuery( selector, context ) )
)
);

你可能感兴趣的:(jQuery源码分析之closest,has,addBack,addSelf,add方法)