转:初探 jQuery 的 Sizzle 选择器
前序
概要
浅析源码
var chunker = /((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^[\]]*\]|['"][^'"]*['"]|[^[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?/g,
((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^\[\]]*\]|['"][^'"]*['"]|[^\[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~]) (\s*,\s*)? ((?:.|\r|\n)*)
1. (?:\((?:\([^()]+\) 2.[^()]+)+\) 3. \[(?:\[[^[\]]*\] 4. [^[\]]+)+\]|\\. 5.[^ >+~,(\[]+)+ 6.[>+~]
1.先查找页面上所有的div 2.循环所有的div,查找每个div下的p 3.合并结果
1.先查找页面上所有的p 2.循环所有的p,查找每个p的父元素 1.如果不是div,遍历上一层。 2.如果已经是顶层,排除此p。 3.如果是div,则保存此p元素。
/:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^-]|$)/ //POS的值
当处于1的情况时:
//如果存在伪类选择符,从selector中移除,并保存在later中
// 这样一来,匹配对象便分离出来:selector(简单选择符存储器)和later(伪类选择符存储器)。
while ( (match = Expr.match.PSEUDO.exec( selector )) ) {
later += match[0];
selector = selector.replace( Expr.match.PSEUDO, "" );
}
//构造selector,并调用Sizzle进行匹配,将结果存储在tmpSet中
selector = Expr.relative[selector] ? selector + "*" : selector;
for ( var i = 0, l = root.length; i < l; i++ ) {
Sizzle( selector, root[i], tmpSet );
}
// 最后便是filter的过滤
return Sizzle.filter( later, tmpSet );
// 这样一来,匹配对象便分离出来:selector(简单选择符存储器)和later(伪类选择符存储器)。
while ( (match = Expr.match.PSEUDO.exec( selector )) ) {
later += match[0];
selector = selector.replace( Expr.match.PSEUDO, "" );
}
//构造selector,并调用Sizzle进行匹配,将结果存储在tmpSet中
selector = Expr.relative[selector] ? selector + "*" : selector;
for ( var i = 0, l = root.length; i < l; i++ ) {
Sizzle( selector, root[i], tmpSet );
}
// 最后便是filter的过滤
return Sizzle.filter( later, tmpSet );
//这个为特例,被正则分割的A数组长度为2,则合并数组元素,上下文则原封不动为Sizzle传递进来的context。
if ( parts.length === 2 && Expr.relative[ parts[0] ] ) {
// 完成一次匹配, 由posProcess 内部调用 filter进行匹配
// 但在匹配前,完成了一次连接选择符的操作
// 存入set,注 set 当前还不是最终的结果,其这里的set和上面的tmpSet一样,都是一个"暂时性"的结果集
set = posProcess( parts[0] + parts[1], context );
if ( parts.length === 2 && Expr.relative[ parts[0] ] ) {
// 完成一次匹配, 由posProcess 内部调用 filter进行匹配
// 但在匹配前,完成了一次连接选择符的操作
// 存入set,注 set 当前还不是最终的结果,其这里的set和上面的tmpSet一样,都是一个"暂时性"的结果集
set = posProcess( parts[0] + parts[1], context );
set = Expr.relative[ parts[0] ] ?
[ context ] :
// 否则对队列首元素进行一次简单匹配操作
Sizzle( parts.shift(), context );
[ context ] :
// 否则对队列首元素进行一次简单匹配操作
Sizzle( parts.shift(), context );
while ( parts.length ) {
// 依次对 所匹配到的 数组中元素进行 递进匹配
selector = parts.shift();
// '>' -> '>input' 的形式
if ( Expr.relative[ selector ] )
selector += parts.shift();
set = posProcess( selector, set );
// 依次对 所匹配到的 数组中元素进行 递进匹配
selector = parts.shift();
// '>' -> '>input' 的形式
if ( Expr.relative[ selector ] )
selector += parts.shift();
set = posProcess( selector, set );
当处于2的情况时:
//为ret绑定正确的返回值
var ret = seed ? //seed 为上一次调用sizzle返回值, 即前文中提到的set|tmpset
//将预匹配后的A数组(parts)中的最后元素设置为ret的expr属性,set属性设为上一次匹配的结果集。
{ expr: parts.pop(), set: makeArray(seed) } :
//如果是第一次调用,则进行匹配操作,调用find函数
// 以parts数组最末元素为当前选择符,进行匹配操作,同时设置与之相关的context
Sizzle.find( parts.pop(), parts.length === 1 && context.parentNode ? context.parentNode : context, isXML(context) );
var ret = seed ? //seed 为上一次调用sizzle返回值, 即前文中提到的set|tmpset
//将预匹配后的A数组(parts)中的最后元素设置为ret的expr属性,set属性设为上一次匹配的结果集。
{ expr: parts.pop(), set: makeArray(seed) } :
//如果是第一次调用,则进行匹配操作,调用find函数
// 以parts数组最末元素为当前选择符,进行匹配操作,同时设置与之相关的context
Sizzle.find( parts.pop(), parts.length === 1 && context.parentNode ? context.parentNode : context, isXML(context) );
if ( document.getElementsByClassName && document.documentElement.getElementsByClassName ) (function(){
// ...
// 如果支持直接获取,则将获取class的方法 直接添加进 Expr.order中 ['ID', 'NAME', 'TAG']
Expr.order.splice(1, 0, "CLASS");
//同时在find中追加对class的获取
Expr.find.CLASS = function(match, context, isXML) {
if ( typeof context.getElementsByClassName !== "undefined" && !isXML ) {
return context.getElementsByClassName(match[1]);
}
};
})();
// ...
// 如果支持直接获取,则将获取class的方法 直接添加进 Expr.order中 ['ID', 'NAME', 'TAG']
Expr.order.splice(1, 0, "CLASS");
//同时在find中追加对class的获取
Expr.find.CLASS = function(match, context, isXML) {
if ( typeof context.getElementsByClassName !== "undefined" && !isXML ) {
return context.getElementsByClassName(match[1]);
}
};
})();
//order: [ "ID", "NAME", "TAG" ]
// 当然,如果浏览器支持对class的直接获取时,order中就会出现class的相关匹配规则
for ( var i = 0, l = Expr.order.length; i < l; i++ ) {
var type = Expr.order[i], match;
// 根据 type 对所传进来的expr 进行正则匹配
// match中通过正则限制了这三类匹配方式的条件。
// 1. ID: /#((?:[\w\u00c0-\uFFFF_-]|\\.)+)/,
// 2. NAME: /\[name=['"]*((?:[\w\u00c0-\uFFFF_-]|\\.)+)['"]*\]/,
// 3. TAG: /^((?:[\w\u00c0-\uFFFF\*_-]|\\.)+)/,
if ( (match = Expr.match[ type ].exec( expr )) ) {
var left = RegExp.leftContext;
//保证返回结果的正确性,如果存在\,则删除
if ( left.substr( left.length - 1 ) !== "\\" ) {
match[1] = (match[1] || "").replace(/\\/g, "");
// 根据type调用 sizzle.selector.find方法获取结果集。
set = Expr.find[ type ]( match, context, isXML );
if ( set != null ) {
//如果匹配成功,删除已经匹配的expr
expr = expr.replace( Expr.match[ type ], "" );
break;
}
}
}
}
return {set: set, expr: expr};
};
// 当然,如果浏览器支持对class的直接获取时,order中就会出现class的相关匹配规则
for ( var i = 0, l = Expr.order.length; i < l; i++ ) {
var type = Expr.order[i], match;
// 根据 type 对所传进来的expr 进行正则匹配
// match中通过正则限制了这三类匹配方式的条件。
// 1. ID: /#((?:[\w\u00c0-\uFFFF_-]|\\.)+)/,
// 2. NAME: /\[name=['"]*((?:[\w\u00c0-\uFFFF_-]|\\.)+)['"]*\]/,
// 3. TAG: /^((?:[\w\u00c0-\uFFFF\*_-]|\\.)+)/,
if ( (match = Expr.match[ type ].exec( expr )) ) {
var left = RegExp.leftContext;
//保证返回结果的正确性,如果存在\,则删除
if ( left.substr( left.length - 1 ) !== "\\" ) {
match[1] = (match[1] || "").replace(/\\/g, "");
// 根据type调用 sizzle.selector.find方法获取结果集。
set = Expr.find[ type ]( match, context, isXML );
if ( set != null ) {
//如果匹配成功,删除已经匹配的expr
expr = expr.replace( Expr.match[ type ], "" );
break;
}
}
}
}
return {set: set, expr: expr};
};
while ( parts.length ) {
var cur = parts.pop(), pop = cur;
// 是否存在 类似这样的匹配 eg: '+', '>'等
if ( !Expr.relative[ cur ] ) {
cur = "";
} else {
//如果存在层间关系的约束 则修复 cur 和pop的指向
// eg ['div', '+', 'span'] => pop = div; cur = '+'; 并进入 relative的匹配。
pop = parts.pop();
}
// 确保拥有上下文 代码略过
Expr.relative[ cur ]( checkSet, pop, isXML(context) );
}
var cur = parts.pop(), pop = cur;
// 是否存在 类似这样的匹配 eg: '+', '>'等
if ( !Expr.relative[ cur ] ) {
cur = "";
} else {
//如果存在层间关系的约束 则修复 cur 和pop的指向
// eg ['div', '+', 'span'] => pop = div; cur = '+'; 并进入 relative的匹配。
pop = parts.pop();
}
// 确保拥有上下文 代码略过
Expr.relative[ cur ]( checkSet, pop, isXML(context) );
}
实例
- jquery.init -> jquery.prototype.find
- 进入Sizzle(对xml的判断) -> 设置parts数组等在匹配中所需要的元素 -> 根据数组长度以及调用origPos进行判断,来决定进入哪个分支,在这个实例下进入分支1
- 循环调用Sizzle进行匹配,将结果存入set中(因为在这一过程中是循环调用,所以对Sizzle的判断也是需要多次,进入哪一分支当然也会是不一样的,比如第二轮循环判断则进入分支2中进行处理) ,对于>号的处理,也会将它合并在其后的span中,构成新的选择符 ‘>span’,然后进入Expr.relative进行匹配,同时调用posProcess。
- 调用Sizzle.find 匹配除伪类以外的部分(即这里的选择器不包含:last),首先会调用Expr.find的find方法来判断是否为哪一类匹配,在这一实例中,为TAG匹配。
- 对从4步中生成的对象进行过滤,匹配’>'(这一步的匹配是由Sizzle.filter触发,由Expr.relative完成),而在匹配’span:last’时则由posProcess来触发,设置later值(:first)以及selector(span),对span的匹配和4步骤一样,重复匹配,而对:first的匹配则是第5步的重头戏,也就是调用Sizzle.filter来完成, 由此便生成了最后的匹配结果。
1.对表达式分组。 2.选择合适的处理顺序。 3.在当前的上下文里过滤要找的节点。并更新上下文。重复这一过程,直到结尾。 4.对结果排序,如果需要的话。 5.返回结果数组。
前向兼容
querySelectorAll
//如果当前document 支持 querySelectorAll方法,则将浏览器可以完成的匹配完全交给浏览器
if ( document.querySelectorAll ) (function(){
var oldSizzle = Sizzle;
// 解决Safari bug 略过 ...
Sizzle = function(query, context, extra, seed){
context = context || document;
// 因为querySelectorAll 在domElement 节点上操作时,存在bug 所以多了这样的判断
// bug info: http://ejohn.org/blog/thoughts-on-queryselectorall/
if ( !seed && context.nodeType === 9 && !isXML(context) ) {
return makeArray( context.querySelectorAll(query), extra );
}
// querySelectorAll 可用则直接返回结果,否则才调用 sizzle
return oldSizzle(query, context, extra, seed);
};
// oldSizzle 方法追加进 新的 Sizzle 中
})();
if ( document.querySelectorAll ) (function(){
var oldSizzle = Sizzle;
// 解决Safari bug 略过 ...
Sizzle = function(query, context, extra, seed){
context = context || document;
// 因为querySelectorAll 在domElement 节点上操作时,存在bug 所以多了这样的判断
// bug info: http://ejohn.org/blog/thoughts-on-queryselectorall/
if ( !seed && context.nodeType === 9 && !isXML(context) ) {
return makeArray( context.querySelectorAll(query), extra );
}
// querySelectorAll 可用则直接返回结果,否则才调用 sizzle
return oldSizzle(query, context, extra, seed);
};
// oldSizzle 方法追加进 新的 Sizzle 中
})();
扩展
// filter的简写 ':'
jQuery.expr[":"] = jQuery.expr.filters;
$.extend($.expr[':'], {
hasSpan: function(e) {
return $(e).find('span').length > 0;
}
});
jQuery.expr[":"] = jQuery.expr.filters;
$.extend($.expr[':'], {
hasSpan: function(e) {
return $(e).find('span').length > 0;
}
});
//直接用就可以了
$('div:hasSpan')....
$('div:hasSpan')....