jQuery Sizzle选择器(二)
自己开始尝试读Sizzle源码。
1、Sizzle同过自执行函数的方式为自己创建了一个独立的作用域,它可以不依赖于jQuery的大环境而独立存在。因此它可以被应用到其它js库中。实现如下:(function(window, undefined){})(window, undefined);
2、Sizzle中最头疼的就是一大片的复杂正则表达式:
// 为正则准备的字符串。匹配选择器中的布尔值,例如 $( "input:checked" );
booleans = "checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped",
// 为正则准备的字符串。匹配所有空白符,包括空格、下一制表符、回车、换行
whitespace = "[\\x20\\t\\r\\n\\f]",
whitespace = "[\\x20\\t\\r\\n\\f]",
// 为正则准备的字符串。匹配unicode编码形式的字符串???
characterEncoding = "(?:\\\\.|[\\w-]|[^\\x00-\\xa0])+",
matchExpr = {
// 匹配ID
"ID": new RegExp( "^#(" + characterEncoding + ")" ),
"ID": new RegExp( "^#(" + characterEncoding + ")" ),
// 匹配class
"CLASS": new RegExp( "^\\.(" + characterEncoding + ")" ),
"CLASS": new RegExp( "^\\.(" + characterEncoding + ")" ),
// 匹配tag
"TAG": new RegExp( "^(" + characterEncoding.replace( "w", "w*" ) + ")" ),
"TAG": new RegExp( "^(" + characterEncoding.replace( "w", "w*" ) + ")" ),
// 匹配attr
"ATTR": new RegExp( "^" + attributes ),
"PSEUDO": new RegExp( "^" + pseudos ),
"ATTR": new RegExp( "^" + attributes ),
"PSEUDO": new RegExp( "^" + pseudos ),
// 匹配child
"CHILD": new RegExp( "^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\(" + whitespace +
"*(even|odd|(([+-]|)(\\d*)n|)" + whitespace + "*(?:([+-]|)" + whitespace +
"*(\\d+)|))" + whitespace + "*\\)|)", "i" ),
"CHILD": new RegExp( "^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\(" + whitespace +
"*(even|odd|(([+-]|)(\\d*)n|)" + whitespace + "*(?:([+-]|)" + whitespace +
"*(\\d+)|))" + whitespace + "*\\)|)", "i" ),
// 匹配boolean
"bool": new RegExp( "^(?:" + booleans + ")$", "i" ),
"needsContext": new RegExp( "^" + whitespace + "*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\(" +
whitespace + "*((?:-\\d)?\\d*)" + whitespace + "*\\)|)(?=[^-]|$)", "i" )
},
// 匹配原生方法和属性
rnative = /^[^{]+\{\s*\[native \w/,
"bool": new RegExp( "^(?:" + booleans + ")$", "i" ),
"needsContext": new RegExp( "^" + whitespace + "*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\(" +
whitespace + "*((?:-\\d)?\\d*)" + whitespace + "*\\)|)(?=[^-]|$)", "i" )
},
// 匹配原生方法和属性
rnative = /^[^{]+\{\s*\[native \w/,
// 匹配id、tag、class,为简单选择器提供快速通道
rquickExpr = /^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,
rquickExpr = /^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,
// 匹配input相关
rinputs = /^(?:input|select|textarea|button)$/i,
rinputs = /^(?:input|select|textarea|button)$/i,
3、看一下Sizzle的入口
// Sizzle的入口,但只处理简单选择器的情况或者浏览器提供了高级接口的情况
function Sizzle( selector, context, results, seed ) { var match, elem, m, nodeType, i, groups, old, nid, newContext, newSelector; // 如果用户传入context,则使用context的ownerDocument和document进行判断,否则使用默认值进行判断。如果是document,则调用setDocument方法
if ( ( context ? context.ownerDocument || context : preferredDoc ) !== document ) {
// 该方法会根据不同的浏览器初始化不同的属性和方法。判断哪些属性时当前浏览器支持的。 setDocument( context ); } // 根据参数初始化context context = context || document;
// 最终需要返回的数据被存放在这里 results = results || []; // 如果没有传入selector,或者selector不是字符串,则直接返回result(这里不返回空数组时因为result可能有在参数中传入值) if ( !selector || typeof selector !== "string" ) { return results; } // 如果context不是元素节点类型,并且也不是document节点类型,返回空数组。 if ( (nodeType = context.nodeType) !== 1 && nodeType !== 9 ) { return []; } // 如果是HTML文档(该参数在setDocument时被初始化),并且参数总传入的seed为空 if ( documentIsHTML && !seed ) { // 检测selector中是否有rquichExpr能够匹配到的字符串 if ( (match = rquickExpr.exec( selector )) ) { // 如果匹配到ID(rquickExpr第一个捕获组中捕获的是ID) if ( (m = match[1]) ) {
// 如果selector是document类型 if ( nodeType === 9 ) {
// 调用gEBI方法 elem = context.getElementById( m ); // 如果得到elem不为空,并且它存在父节点(如果不存在父节点,则可能是不存在于DOM树中的文档片段) if ( elem && elem.parentNode ) { // 因为调用gEBI在某些(哪些呢?)浏览器中得到属性name也是m的元素,所以需要过滤掉 if ( elem.id === m ) {
// 将符合条件的元素push进results results.push( elem ); return results; } } else { return results; } } else { // 如果context不是document节点,得到context所在的文档的document节点,并调用其gEBI方法 if ( context.ownerDocument && (elem = context.ownerDocument.getElementById( m )) &&
// 暂时还不知道contains做了什么
contains( context, elem ) && elem.id === m ) { results.push( elem ); return results; } } // 如果匹配到的是tag(rquickExpr第一个捕获组中捕获的是tag) } else if ( match[2] ) {
// 直接调用gEBTN方法得到结果并push到results中返回 push.apply( results, context.getElementsByTagName( selector ) ); return results; // 如果匹配到的是class(rquickExpr第一个捕获组中捕获的是class)并且该浏览器支持gEBCN方法,则调用gEBCN方法 } else if ( (m = match[3]) && support.getElementsByClassName && context.getElementsByClassName ) {
// 将结果push到results中返回 push.apply( results, context.getElementsByClassName( m ) ); return results; } } // 如果浏览器支持querySelectorAll方法 if ( support.qsa && (!rbuggyQSA || !rbuggyQSA.test( selector )) ) { nid = old = expando; newContext = context; newSelector = nodeType === 9 && selector; if ( nodeType === 1 && context.nodeName.toLowerCase() !== "object" ) { groups = tokenize( selector ); if ( (old = context.getAttribute("id")) ) { nid = old.replace( rescape, "\\$&" ); } else { context.setAttribute( "id", nid ); } nid = "[id='" + nid + "'] "; i = groups.length; while ( i-- ) { groups[i] = nid + toSelector( groups[i] ); } newContext = rsibling.test( selector ) && context.parentNode || context; newSelector = groups.join(","); } if ( newSelector ) { try { push.apply( results, newContext.querySelectorAll( newSelector ) ); return results; } catch(qsaError) { } finally { if ( !old ) { context.removeAttribute("id"); } } } } } // 其余的情况,交给select来处理 return select( selector.replace( rtrim, "$1" ), context, results, seed ); }