jQuery选择器源码解读(一):Sizzle方法
对jQuery的Sizzle各方法做了深入分析(同时也参考了一些网上资料)后,将结果分享给大家。我将采用连载的方式,对Sizzle使用的一些方法详细解释一下,每篇文章介绍一个方法。
若需要转载,请写明出处,多谢。
/* *Sizzle方法是Sizzle选择器包的主要入口,jQuery的find方法就是调用该方法获取匹配的节点 *该方法主要完成下列任务: *1、对于单一选择器,且是ID、Tag、Class三种类型之一,则直接获取并返回结果 *2、对于支持querySelectorAll方法的浏览器,通过执行querySelectorAll方法获取并返回匹配的DOM元素 *3、除上之外则调用select方法获取并返回匹配的DOM元素 * * *@paramselector选择器字符串 *@paramcontext执行匹配的最初的上下文(即DOM元素集合)。若context没有赋值,则取document。 *@paramresults已匹配出的部分最终结果。若results没有赋值,则赋予空数组。 *@paramseed初始集合 */ functionSizzle(selector,context,results,seed){ varmatch,elem,m,nodeType, //QSAvars i,groups,old,nid,newContext,newSelector; /* *preferredDoc=window.document * *setDocument方法完成一些初始化工作 */ if((context?context.ownerDocument||context:preferredDoc)!==document){ setDocument(context); } context=context||document; results=results||[]; /* *若selector不是有效地字符串类型数据,则直接返回results */ if(!selector||typeofselector!=="string"){ returnresults; } /* *若context既不是document(nodeType=9),也不是element(nodeType=1),那么就返回空集合 */ if((nodeType=context.nodeType)!==1&&nodeType!==9){ return[]; } //若当前过滤的是HTML文档,且没有设定seed,则执行if内的语句体 if(documentIsHTML&&!seed){ /* *若选择器是单一选择器,且是ID、Tag、Class三种类型之一,则直接获取并返回结果 * *rquickExpr=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/ *上述正则表达式括号内三段依次分别用来判断是否是ID、TAG、CLASS类型的单一选择器 *上述正则表达式在最外层圆括号内有三个子表达式(即三个圆括号括起来的部分), *分别代表ID、Tag、Class选择器的值,在下面代码中,分别体现在match[1]、match[2]、match[3] */ if((match=rquickExpr.exec(selector))){ //Speed-up:Sizzle("#ID") //处理ID类型选择器,如:#ID if((m=match[1])){ //若当前上下文是一个document,则执行if内语句体 if(nodeType===9){ elem=context.getElementById(m); //CheckparentNodetocatchwhenBlackberry4.6 //returns //nodesthatarenolongerinthedocument#6963 if(elem&&elem.parentNode){ //HandlethecasewhereIE,Opera,andWebkit //returnitems //bynameinsteadofID /* *一些老版本的浏览器会把name当作ID来处理, *返回不正确的结果,所以需要再一次对比返回节点的ID属性 */ if(elem.id===m){ results.push(elem); returnresults; } }else{ returnresults; } }else{ //Contextisnotadocument /* *contains(context,elem)用来确认获取的elem是否是当前context对象的子对象 */ if(context.ownerDocument &&(elem=context.ownerDocument.getElementById(m)) &&contains(context,elem)&&elem.id===m){ results.push(elem); returnresults; } } //Speed-up:Sizzle("TAG") //处理Tag类型选择器,如:SPAN }elseif(match[2]){ push.apply(results,context.getElementsByTagName(selector)); returnresults; //Speed-up:Sizzle(".CLASS") /* *处理class类型选择器,如:.class *下面条件判断分别是: *m=match[3]:有效的class类型选择器 *support.getElementsByClassName该选择器的div支持getElementsByClassName *context.getElementsByClassName当前上下文节点有getElementsByClassName方法 * */ }elseif((m=match[3])&&support.getElementsByClassName &&context.getElementsByClassName){ push.apply(results,context.getElementsByClassName(m)); returnresults; } } //QSApath /* *若浏览器支持querySelectorAll方法且选择器符合querySelectorAll调用标准,则执行if内语句体 *在这里的检查仅仅是简单匹配 *第一次调用Sizzle时,rbuggyQSA为空 * *if语句体内对当前context对象的id的赋值与恢复,是用来修正querySelectorAll的一个BUG *该BUG会在某些情况下把当前节点(context)也作为结果返回回来。 *具体方法是,在现有的选择器前加上一个属性选择器:[id=XXX], *XXX为context的id,若context本身没有设置id,则给个默认值expando。 */ if(support.qsa&&(!rbuggyQSA||!rbuggyQSA.test(selector))){ nid=old=expando; newContext=context; //若context是document,则newSelector取自selector,否则为false newSelector=nodeType===9&&selector; //qSAworksstrangelyonElement-rootedqueries //WecanworkaroundthisbyspecifyinganextraIDonthe //root //andworkingupfromthere(ThankstoAndrewDupontfor //thetechnique) //IE8doesn'tworkonobjectelements if(nodeType===1&&context.nodeName.toLowerCase()!=="object"){ groups=tokenize(selector); if((old=context.getAttribute("id"))){ /* *rescape=/'|\\/g, *这里将old中的单引号、竖杠、反斜杠前加一个反斜杠 *old.replace(rescape,"\\$&")代码中的$&代表匹配项 */ nid=old.replace(rescape,"\\$&"); }else{ context.setAttribute("id",nid); } nid="[id='"+nid+"']"; //重新组合新的选择器 i=groups.length; while(i--){ groups[i]=nid+toSelector(groups[i]); } /* *rsibling=newRegExp(whitespace+"*[+~]") *rsibling用于判定选择器是否存在兄弟关系符 *若包含+~符号,则取context的父节点作为当前节点 */ newContext=rsibling.test(selector)&&context.parentNode ||context; newSelector=groups.join(","); } if(newSelector){ /* *这里之所以需要用try...catch, *是因为jquery所支持的一些选择器是querySelectorAll所不支持的, *当使用这些选择器时,querySelectorAll会报非法选择器, *故需要jquery自身去实现。 */ try{ //将querySelectorAll获取的结果并入results,而后返回resulsts push.apply(results,newContext .querySelectorAll(newSelector)); returnresults; }catch(qsaError){ }finally{ if(!old){ context.removeAttribute("id"); } } } } } //Allothers //除上述快捷方式和调用querySelectorAll方式直接获取结果外,其余都需调用select来获取结果 /* *rtrim=newRegExp("^"+whitespace+"+|((?:^|[^\\\\])(?:\\\\.)*)" * +whitespace+"+$","g"), *whitespace="[\\x20\\t\\r\\n\\f]"; *上述rtrim正则表达式的作用是去掉selector两边的空白,空白字符由whitespace变量定义 *rtrim的效果与newRegExp("^"+whitespace+"+|"+whitespace+"+$","g")相似 */ returnselect(selector.replace(rtrim,"$1"),context,results,seed); }
各位朋友,若觉得写得不错,帮我顶一下,给点动力,多谢!