Jquery 1.9.1源码分析系列(十二)之筛选操作
废话不多说了直接奔入主题了。
jQuery.fn.find(selector)
find接受一个参数表达式selector:选择器(字符串)、DOM元素(Element)、jQuery对象。分两种情况处理:
第一种,如果传入的参数是非字符串,则先通过jQuery选择器将selector查找出来,然后过滤出包含于当前jQuery对象所匹配的元素的节点。
if(typeofselector!=="string"){
self=this;
returnthis.pushStack(jQuery(selector).filter(function(){
for(i=0;i<len;i++){
if(jQuery.contains(self[i],this)){
returntrue;
}
}
}));
}
可以看出过滤条件中jQuery.contains(self[i],this)是关键,该函数使用的是Sizzle选择器中的函数,在Sizzle引擎中有分析,详情点击。
第二种,如果选择器是字符串,调用jQuery.find(=Sizzle)直接处理
ret=[];
for(i=0;i<len;i++){
//第二个参数是表示context
jQuery.find(selector,this[i],ret);
}
//$(selector,context)变成$(context).find(selector),需要去重和pushStack
ret=this.pushStack(len>1?jQuery.unique(ret):ret);
ret.selector=(this.selector?this.selector+"":"")+selector;
returnret;
jQuery.fn.closest(selectors,context)
第二个参数是可选的。函数用于从当前匹配元素开始,逐级向上级选取符合指定表达式的第一个元素,并以jQuery对象的形式返回。
这里的表达式包括:选择器(字符串)、DOM元素(Element)、jQuery对象。
代码中的处理步骤为
1.根据传递的参数先查询出结果保存在pos中。
pos=rneedsContext.test(selectors)||typeofselectors!=="string"? jQuery(selectors,context||this.context): 0;
2.遍历当前jQuery对象的每一个元素,从这个元素开始,逐级向上级选取符合指定表达式的第一个祖先元素。
for(;i<l;i++){
cur=this[i];
while(cur&&cur.ownerDocument&&cur!==context&&cur.nodeType!==11){
if(pos?pos.index(cur)>-1:jQuery.find.matchesSelector(cur,selectors)){
ret.push(cur);
break;
}
cur=cur.parentNode;
}
}
returnthis.pushStack(ret.length>1?jQuery.unique(ret):ret);
parents()和.closest()方法类似,它们都沿DOM树向上遍历。但区别也很大closest找到第一个符合条件就截止,parents是找到所有符合条件的集合。
jQuery.fn.parent/parents/parentsUntil/next/prev/nextAll/prevAll/nextUntil/prevUntil/siblings/children/contents详解
以上几组筛选被放在一起处理,源码如下
jQuery.each({
parent:function(elem){…},
parents:function(elem){…},
parentsUntil:function(elem,i,until){…},
next:function(elem){…},
prev:function(elem){…},
nextAll:function(elem){…},
prevAll:function(elem){…},
nextUntil:function(elem,i,until){…},
prevUntil:function(elem,i,until){…},
siblings:function(elem){…},
children:function(elem){…},
contents:function(elem){…}
},function(name,fn){
jQuery.fn[name]=function(until,selector){
varret=jQuery.map(this,fn,until);
//过滤
...
returnthis.pushStack(ret);
};
});
可以看出,这几个筛选步骤一致。都是先通过map函数把当前jQuery对象每个匹配的元素代入相应的匹配函数(fn)中获取出结果然后在进行后续的过滤。
我们先看一下后面的过滤(已经通过jQuery.map(this,fn,until)获取到了备选种子ret)
首先,并不是所有的筛选函数都有until这个参数,只有以Until结尾的几个筛选才需要这个参数,其他的筛选只有selector这个参数。
if(!runtil.test(name)){
selector=until;
}
其次,如果有选择器,则通过选择器过滤一下先前查找结果ret
if(selector&&typeofselector==="string"){
ret=jQuery.filter(selector,ret);
}
然后,guaranteedUnique里面的几种筛选条件(children/contents/next/prev)在当前jQuery对象所匹配的元素个数有多个的时候,通过每个匹配元素获取到的结果保存在结果集ret中,且不需要去重。其他筛选是要去重的。点击查看jQuery.unique方法详解
ret=this.length>1&&!guaranteedUnique[name]?jQuery.unique(ret):ret;
另外,还需要处理的特殊情况是:如果当前jQuery对象所匹配的元素有多个,则使用parents/prevUntil/prevAll这三种筛选的结果需要倒序排列。需要倒序的原因:jQuery.unique使用的是Sizzle引擎中的排序函数Sizzle.uniqueSort,这个排序函数会根据文档最顶层对象到最底层的方式排列。
if(this.length>1&&rparentsprev.test(name)){
ret=ret.reverse();
}
最后,返回包裹后的结果
returnthis.pushStack(ret);
上面说了主题的框架结构,下面说一下这一组筛选器匹配函数里面用到的两个函数jQuery.dir和jQuery.sibling,直接上源码
//从当前元素elem指定的dir对应的节点开始一直查找dir,并将这些节点保存在matched中,直到循环终止。注意:结果中不包含elem节点
dir:function(elem,dir,until){
varmatched=[],
cur=elem[dir];
while(cur&&cur.nodeType!==9&&(until===undefined||cur.nodeType!==1||!jQuery(cur).is(until))){
if(cur.nodeType===1){
matched.push(cur);
}
cur=cur[dir];
}
returnmatched;
},
//获取节点n及其兄弟节点中非elem的节点集合r
sibling:function(n,elem){
varr=[];
for(;n;n=n.nextSibling){
if(n.nodeType===1&&n!==elem){
r.push(n);
}
}
returnr;
}
//找到当前元素cur的下一个dir为止
functionsibling(cur,dir){
do{
cur=cur[dir];
}while(cur&&cur.nodeType!==1);
returncur;
}
jQuery.fn.add(selector,context)和jQuery.fn.addBack(selector)
add函数是向当前匹配元素中添加符合指定表达式的元素,并以jQuery对象的形式返回。add可以接收包括:选择器(字符串)、HTML内容(字符串)、DOM元素(Element)、jQuery对象。处理比较简单,直接上源码
add:function(selector,context){
varset=typeofselector==="string"?
jQuery(selector,context):
jQuery.makeArray(selector&&selector.nodeType?[selector]:selector),
//把selector表达式获取的结果集拼接到当前对象上
all=jQuery.merge(this.get(),set);
//返回新的拼接结果
returnthis.pushStack(jQuery.unique(all));
}
jQuery.fn.add和jQuery.fn.not相对应。jQuery.fn.not后面再说。
jQuery.fn.addBack将之前匹配的元素加入到当前匹配的元素中,并以新的jQuery对象的形式返回。
addBack:function(selector){
returnthis.add(selector==null?
this.prevObject:this.prevObject.filter(selector)
);
}
jQuery.fn.andSelf=jQuery.fn.addBack;
jQuery.fn.not(selector)和jQuery.fn.filter(selector)
not:function(selector){
returnthis.pushStack(winnow(this,selector,false));
}
filter:function(selector){
returnthis.pushStack(winnow(this,selector,true));
},
not和filter都是操作本身的集合,not是过滤掉本身集合中满足过滤条件selector的项,留下其他项。而filter是留下满足过滤条件selector的项。
关键是functionwinnow(elements,qualifier,keep)函数。这个函数的功能是执行相同的过滤或者不过滤的功能。过滤条件qualifier有三种:函数、DOM节点、字符串。keep:true表示保留满足过滤条件的项,false表示保留不满足过滤条件的项。
winnow的源码注释如下
//执行相同的过滤或者不过滤的功能
functionwinnow(elements,qualifier,keep){
//Can'tpassnullorundefinedtoindexOfinFirefox4
//Setto0toskipstringcheck
qualifier=qualifier||0;
//如果过滤条件是函数,则通过过滤函数过滤
if(jQuery.isFunction(qualifier)){
returnjQuery.grep(elements,function(elem,i){
varretVal=!!qualifier.call(elem,i,elem);
returnretVal===keep;
});
//如果过滤条件是DOM相关类型,通过比较节点是否相同来过滤
}elseif(qualifier.nodeType){
returnjQuery.grep(elements,function(elem){
return(elem===qualifier)===keep;
});
//如果过滤条件是字符串
}elseif(typeofqualifier==="string"){
//过滤出elements中的节点元素
varfiltered=jQuery.grep(elements,function(elem){
returnelem.nodeType===1;
});
//其中isSimple=/^.[^:#\[\.,]*$/
if(isSimple.test(qualifier)){
returnjQuery.filter(qualifier,filtered,!keep);
}else{
//查找filtered中满足筛选条件qualifier的节点
qualifier=jQuery.filter(qualifier,filtered);
}
}
//过滤出elements中满足过滤条件的元素
returnjQuery.grep(elements,function(elem){
return(jQuery.inArray(elem,qualifier)>=0)===keep;
});
}
其中用到jQuery.grep,grep详解点击这里。
jQuery.filter(expr,elems,not)这个低级api专门用来处理jQuery.fn.filter中过滤条件为字符串的情况。
jQuery.filter:function(expr,elems,not){
if(not){
expr=":not("+expr+")";
}
//其中matchesSelector和matches是Sizzle中的函数。matchesSelector是判断单个元素elem是否满足表达式expr,matches是查找元素集合elems中满足表达式expr的项
returnelems.length===1?
jQuery.find.matchesSelector(elems[0],expr)?[elems[0]]:[]:
jQuery.find.matches(expr,elems);
},
jQuery.fn.index(elem)
index函数实际上是一个多功能函数的集合。
第一个功能:不传递elem参数,则表示取当前jQuery对象(jQuery对象的第一个元素)在其所有同辈元素中的位置。
if(!elem){
return(this[0]&&this[0].parentNode)?this.first().prevAll().length:-1;
}
第二个功能:如果参数为String类型则将其视作选择器,返回当前元素在选择器所匹配的元素中的索引位置。如果该选择器不匹配任何元素或者当前元素不在匹配到的元素内,则返回-1。
if(typeofelem==="string"){
//在数组jQuery(elem)中搜索指定的值,并返回其索引值
returnjQuery.inArray(this[0],jQuery(elem));
}
第三个功能:如果object为DOM元素或jQuery对象,则返回该元素(或该jQuery对象中的第一个元素)在当前jQuery对象所匹配的元素中的索引位置。
returnjQuery.inArray(elem.jquery?elem[0]:elem,this);
其他的筛选处理就不分析了。看源码即可明白。
jquery选择器