为了很清楚的理解parents,parentsUntil,next,prev,nextAll,prevAll,nextUntil,prevUntil,siblings,children,contents我们把这些实例函数提取出来测试,源码如下:
jQuery.dir方法:
dir: function( elem, dir, until ) { //用于存储结果数组 var matched = [], cur = elem[ dir ]; //如果nextUntil等没有传入until,那么和parents方法一样,因为parents最多只能一个参数.只是后until就是undefined,可以一直往上调用parentNode //判断当前jQuery对象所匹配的元素是否符合指定的表达式只要其中有至少一个元素符合该表达式就返回 //如果当前父元素已经符合until表达式了,那么不会被添加进去! while ( cur && cur.nodeType !== 9 && (until === undefined || cur.nodeType !== 1 || !jQuery( cur ).is( until )) ) { if ( cur.nodeType === 1 ) { matched.push( cur ); } cur = cur[dir]; } return matched; }
jQuery.siblings方法源码:(第二个参数是结束参数)
sibling: function( n, elem ) { var r = []; for ( ; n; n = n.nextSibling ) { if ( n.nodeType === 1 && n !== elem ) { r.push( n ); } } return r; }
sibling方法:
function sibling( cur, dir ) {//对于next/pre方法最终调用的是该方法,传入的参数如sibling(elem,"previousSibling") do { cur = cur[ dir ]; } while ( cur && cur.nodeType !== 1 ); return cur; }
parents方法源码:
jQuery.each({ parent: function( elem ) { var parent = elem.parentNode; return parent && parent.nodeType !== 11 ? parent : null; }, //dir是parentNode,调用var $parents3 = $p.parents(".bar"); //这里的parents方法接受选择器$p的每一个DOM元素$p[i],同时我们下面说过的第三个参数until也会传入作为arguments[1] //因为下面的调用方式是$p.parents(".bar");所以until是".bar"这里的arguments[1]就是".bar" parents: function( elem ) { return jQuery.dir( elem, "parentNode" ); }, parentsUntil: function( elem, i, until ) { return jQuery.dir( elem, "parentNode", until ); }, next: function( elem ) { return sibling( elem, "nextSibling" ); }, prev: function( elem ) { return sibling( elem, "previousSibling" ); }, nextAll: function( elem ) { return jQuery.dir( elem, "nextSibling" ); }, prevAll: function( elem ) { return jQuery.dir( elem, "previousSibling" ); }, //nextUntil函数,第一个参数是$p[i],第二个参数是该DOM元素的下标,util就是传入的额外参数 nextUntil: function( elem, i, until ) { return jQuery.dir( elem, "nextSibling", until ); }, //prevUntil函数 prevUntil: function( elem, i, until ) { return jQuery.dir( elem, "previousSibling", until ); }, siblings: function( elem ) { return jQuery.sibling( ( elem.parentNode || {} ).firstChild, elem ); }, children: function( elem ) { return jQuery.sibling( elem.firstChild ); }, contents: function( elem ) { return jQuery.nodeName( elem, "iframe" ) ? elem.contentDocument || elem.contentWindow.document : jQuery.merge( [], elem.childNodes ); } }, function( name, fn ) { //jQuery.fn[ "parents"]等函数构建,接受两个参数。如果这么调用$p.parents(".bar");只是传入一个参数 jQuery.fn[ name ] = function( until, selector ) { /*用$p的选择结果不断调用fn方法,在这里就是调用parents方法,返回的结果是Array类型,until是第一个参数,这里是".bar"。这里的this就是$p,第二个表示执行的函数fn,这里是parents方法,在这个参数里面每次会传入this[i],也就是DOM对象,因此在上面的parents里面会传入该DOM对象。同时该fn方法还会传入该DOM对象的下标,第三个参数是$.map额外参数,他也会传入fn里面,哪怕没有形参接受他,详见jQuery.map用法。如果调用fn返回的结果是true,添加到ret集合中间。*/ var ret = jQuery.map( this, fn, until ); //如果不是parentsUntil等,那么规定只能接受一个参数,其中的selector就是上面的第一个参数until,这里是".bar" //如果是parentsUtil那么可以传入两个参数,第一个是until,这时候会把until穿进去上面的函数,dir函数 if ( name.slice( -5 ) !== "Until" ) { selector = until; } //如果selector存在,那么用selector对选择的结果数组ret进行过滤! if ( selector && typeof selector === "string" ) { ret = jQuery.filter( selector, ret ); } //如果$p的选择的个数大于1,那么防止$p[0],$p[i]等选择结果有重复要去重复! if ( this.length > 1 ) { // Remove duplicates if ( !guaranteedUnique[ name ] ) { ret = jQuery.unique( ret ); } // Reverse order for parents* and prev-derivatives</span> //var rparentsprev = /^(?:parents|prev(?:Until|All))/,</span> //对含有parents,prev前缀进行逆向输出 if ( rparentsprev.test( name ) ) { ret = ret.reverse(); } } return this.pushStack( ret ); }; });
注意:其它的后缀不是until的方法,都只能传入一个参数,该参数就是selector表示对选择的结果进行选择。这里的jQuery.map传入了第三个参数,其中的fn会传入三个参数,分别为:DOM元素,DOM元素的下标,多余的until参数用于prevUntil等until方法作为停止搜索的标志!
next: function( elem ) { return sibling( elem, "nextSibling" ); }next获取的是调用对象的所有的下一个同级的对象组成的集合,为什么能够把所有的DOM的下一个兄弟节点组成一个真正的集合,来自于 jQuery.map的作用。其实在遍历每一个DOM的下一个兄弟节点的时候返回的是一个数组,在jQuery.map中调用了[].concat.apply([],ret);
nextAll: function( elem ) { return jQuery.dir( elem, "nextSibling" ); }在next中调用的是siblings,该方法只会获取同方向的一个元素。而nextAll调用的是jQuery.dir,他会获取指定方向的所有的元素,知道until结束
function sibling( cur, dir ) {//对于next/pre方法最终调用的是该方法,传入的参数如sibling(elem,"previousSibling") do { cur = cur[ dir ]; } while ( cur && cur.nodeType !== 1 ); return cur; }prev方法获取指定的方向的一个元素,调用的依然是siblings方法
prev: function( elem ) { return sibling( elem, "previousSibling" ); }而prevAll调用的是jQuery.dir,该方法获取同方向的所有元素
prevAll: function( elem ) { return jQuery.dir( elem, "previousSibling" ); }parent元素获取 直接父元素
parent: function( elem ) { var parent = elem.parentNode; return parent && parent.nodeType !== 11 ? parent : null; }而parents依然用jQuery.dir获取指定方向的所有的元素
parents: function( elem ) { return jQuery.dir( elem, "parentNode" ); }nextAll获取同方向的所有元素
nextAll: function( elem ) { return jQuery.dir( elem, "nextSibling" ); }jQuery.siblings用于获取所有的同级元素
sibling: function( n, elem ) { var r = []; for ( ; n; n = n.nextSibling ) { if ( n.nodeType === 1 && n !== elem ) {//不包含自身 r.push( n ); } } return r; }我们看看siblings方法
siblings: function( elem ) { return jQuery.sibling( ( elem.parentNode || {} ).firstChild, elem ); }该函数不会返回自身,只是返回自身所有的同级元素!
children: function( elem ) { return jQuery.sibling( elem.firstChild ); }contents实例方法如果是iframe获取contentWindow.document或者contentDocument。如果不是iframe那么结果包含所有的 childNodes集合
contents: function( elem ) { return jQuery.nodeName( elem, "iframe" ) ? elem.contentDocument || elem.contentWindow.document : jQuery.merge( [], elem.childNodes ); }对于所有的until结尾的元素,都会给jQuery.dir传入第三个参数表示结束搜索的元素。 当我们调用的时候,如果只是给该方法传入一个参数那么该参数就是until,如果传入两个参数第一个参数是until,第二个参数是selector,也就是until类型的方法第一个参数必定是until!
parentsUntil: function( elem, i, until ) { return jQuery.dir( elem, "parentNode", until ); }prevUtil获取所有的同级元素直到指定的元素为止
prevUntil: function( elem, i, until ) { return jQuery.dir( elem, "previousSibling", until ); }nextUtil获取指定的同级元素,直到特定的元素结束
nextUntil: function( elem, i, until ) { return jQuery.dir( elem, "nextSibling", until ); }
总结:
(1)parents等方法首先是为每一个调用对象的DOM对象都调用相应的dir或者siblings方法,得到一个结果数组.然后用parents等方法的参数作为选择器对结果数组进行筛选,得到有效的结果! 对于parentsUtils来说,可能会传入第二个参数util,那么在dir方法里面到该util参数则终止parentNode的移动,同时把返回的结果用第一个参数去重。思路:先选择后去重
(2)如果nextUtil等只是传入了一个参数,那么这个参数就是until,如果传入了两个参数那么第一个参数是until,第二个参数是selector(也就是说这种情况说明了有Until那么第一个参数必定是until),用于对上一步选择的结果进行进一步筛选!如果不是until类型,那么只会传入一个参数,这个参数就是selector!
(3)我们可以看到until时候传入的第三个参数会原封不动的传入到回调函数中,所以他应该是DOM对象,而不是jQuery对象,这一点要注意一下
var result=$("#n7").prevUntil($('#n5')[0]);//DOM对象 console.log(result);