转载:http://www.baiduux.com/blog/2010/07/15/the_sizzle_in_jquery/
前序
概要
浅析源码
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 ) ;
//这个为特例,被正则分割的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 ) ;
set = Expr. relative [ parts [ 0 ] ] ?
[ context ] :
// 否则对队列首元素进行一次简单匹配操作
Sizzle ( parts. shift ( ) , context ) ;
while ( parts. length ) {
// 依次对 所匹配到的 数组中元素进行 递进匹配
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 ) ) ;
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 ] ) ;
}
} ;
} ) ( ) ;
//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 } ;
} ;
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 ) ) ;
}
实例
- 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 中
} ) ( ) ;
扩展
// filter的简写 ':'
jQuery. expr [ ":" ] = jQuery. expr. filters ;
$. extend ($. expr [ ':' ] , {
hasSpan : function (e ) {
return $ (e ). find ( 'span' ). length > 0 ;
}
} ) ;
//直接用就可以了
$ ( 'div:hasSpan' )....