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);
}
各位朋友,若觉得写得不错,帮我顶一下,给点动力,多谢!