/*
*tokenize方法是选择器解析的核心函数,它将选择器转换成两级数组groups
*举例:
*若选择器为“div.class,span”,则解析后的结果为:
*group[0][0]={type:'TAG',value:'div',matches:match}
*group[0][1]={type:'CLASS',value:'.class',matches:match}
*group[1][0]={type:'TAG',value:'span',matches:match}
*由上述结果可以看出,groups的每一个元素以逗号分隔的选择器块的解析结果,
*另外,上述结果中的matches等于模式匹配的结果,由于在此不方便写清楚,
*故只把代码matches:match写在这里。
*
*tokenize方法完成如下两个主要任务:
*1、解析选择器
*2、将解析结果存入缓存中,以备后用
*
*
*@paramselector待解析的选择器字符串
*@paramparseOnly为true时,说明本次调用是匹配子选择器
*举个例子:若初始选择器为"div:not(.class:not(:eq(4))):eq(3)"
*代码首先匹配出TAG选择器div,
*之后匹配出的pseudo选择器字符串是:not(.class:not(:eq(4))):eq(3),
*代码会把“.class:not(:eq(4))):eq(3”作为not的括号内的值进一步进行解析,
*此时代码在调用tokenize解析时,parseOnly参数会传入true.
*/
functiontokenize(selector,parseOnly){
varmatched,match,tokens,type,soFar,groups,preFilters,
//获取缓存中的结果
cached=tokenCache[selector+""];
/*
*若缓存中有selector对应的解析结果
*则执行if中语句体
*/
if(cached){
//若是对初始选择器解析(parseOnly!=true),则返回缓存结果,
//若不是,则返回0
returnparseOnly?0:cached.slice(0);
}
/*
*由于字符串在javascript中不是作为对象来处理的,
*所以通过赋值,代码就自动复制了一个新字符串给了soFar,
*这样,对soFar的任何处理都不会影响selector的原有数据
*/
soFar=selector;
groups=[];
//此处赋值,仅仅用于减少后续代码字数,缩短执行路径
preFilters=Expr.preFilter;
while(soFar){
//Commaandfirstrun
/*
*rcomma=newRegExp("^"+whitespace+"*,"+whitespace+"*")
*rcomma用来判定是否存在多个选择器块,即用逗号隔开的多个并列的选择器
*
*下面条件判定依次为:
*!matched:若是第一次执行循环体,则为true;否则为false。
*这里matched即作为是否第一次执行循环体的标识,
*也作为本次循环中soFar是否以非法字符串(即非合法单一选择器)开头的标志。
*(match=rcomma.exec(soFar):获取符合rcomma的匹配项
*/
if(!matched||(match=rcomma.exec(soFar))){
if(match){
//Don'tconsumetrailingcommasasvalid
/*
*剔除掉第一个逗号及之前的所有字符
*举个例子:
*若初始选择器为:"div.news,span.closed",
*在解析过程中,首先由后续代码解析完毕div.news,剩下",span.closed"
*在循环体内执行到这里时,将逗号及之前之后连续的空白(match[0])删除掉,
*使soFar变成"span.closed",继续执行解析过程
*
*在这里,若初始选择器的最后一个非空白字符是逗号,
*那么执行下面代码时soFar不变,即soFar.slice(match[0].length)返回空字符串,
*故最终返回的是||后面的soFar
*/
soFar=soFar.slice(match[0].length)||soFar;
}
/*
*在第一次执行循环体或者遇到逗号分割符时,将tokens赋值为一个空数组,
*同时压入groups数组
*/
groups.push(tokens=[]);
}
matched=false;
//Combinators
/*
*rcombinators=newRegExp(
* "^"+whitespace+"*([>+~]|"+whitespace+")"+whitespace+"*"),
*rcombinators用来匹配四种关系符,即>+~和空白
*
*若soFar中是以关系符开始的,则执行if内的语句体
*/
if((match=rcombinators.exec(soFar))){
/*
*将match[0]移除match数组,同时将它赋予matched
*若原本关系符两边带有空格,则此时match[0]与matched是不相等的
*举个例子:
*若soFar="+.div";
*执行match=rcombinators.exec(soFar)后,
*match[0]="+",而match[1]="+";
*执行完matched=match.shift()后,
*matched="+",而match[0]="+";
*/
matched=match.shift();
//将匹配结果压入tokens数组中
tokens.push({
value:matched,
//Castdescendantcombinatorstospace
/*
*rtrim=newRegExp("^"+whitespace+"+|((?:^|[^\\\\])(?:\\\\.)*)"
* +whitespace+"+$","g"),
*whitespace="[\\x20\\t\\r\\n\\f]";
*
*下面match[0].replace(rtrim,"")的作用是将match[0]左右两边的空白替换为空格
*但是由于其上的match.shift的作用,match[0]已经是两边不带空白的字符串了,
*故此出的替换是没有用途的代码
*/
type:match[0].replace(rtrim,"")
});
//将关系符之后的字符串赋予soFar,继续解析
soFar=soFar.slice(matched.length);
}
//Filters
/*
*下面通过for语句对soFar逐一匹配ID、TAG、CLASS、CHILD、ATTR、PSEUDO类型的选择器
*若匹配到了,则先调用该类型选择器对应的预过滤函数,
*然后,将结果压入tokens数组,继续本次循环。
*/
for(typeinExpr.filter){
/*
*match=matchExpr[type].exec(soFar):对soFar调用type类型的正则表达式对soFar进行匹配,
*并将匹配结果赋予match。若未匹配到数据,则match为undefined。
*!preFilters[type]:若不存在type类型的预过滤函数,则为true
*match=preFilters[type](match):执行预过滤,并将结果返回给match
*
*/
if((match=matchExpr[type].exec(soFar))
&&(!preFilters[type]||(match=preFilters[type]
(match)))){
//将match[0]移除match数组,同时将它赋予matched
matched=match.shift();
//将匹配结果压入tokens数组中
tokens.push({
value:matched,
type:type,
matches:match
});
//将匹配结果之后的字符串赋予soFar,继续解析
soFar=soFar.slice(matched.length);
}
}
/*
*若matched==false,
*则说明本次循环没有有效的选择器(包括关系符和id、class等类型选择器)
*因此,解析到当前位置遗留下来的soFar是非法的选择器字符串
*跳出while循环体
*/
if(!matched){
break;
}
}
//Returnthelengthoftheinvalidexcess
//ifwe'rejustparsing
//Otherwise,throwanerrororreturntokens
/*
*若不是对初始选择器字符串进行解析(!parseOnly==true),
*则返回soFar.length,此时的soFar.length代表连续有效的选择器最终位置,
*后续文章将以实例进行说明
*若是对初始选择器字符串进行解析,则看soFar是否还有字符,
*若是,则执行Sizzle.error(selector)抛出异常;
*若不是,则执行tokenCache(selector,groups).slice(0)将结果压入缓存,并返回结果的副本。
*/
returnparseOnly?soFar.length:soFar?Sizzle.error(selector):
//Cachethetokens
tokenCache(selector,groups).slice(0);
}