请提前阅读正则表达式,同时请提前阅读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中元素的父元素!
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对象的形式返回。
//调用方式: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 ) ) ) );