Sizzle源码分析(四) Sizzle静态方法分析

877 阅读9分钟

04.jpeg

简介

Sizzle 的静态方法起了辅助的作用,帮助完成一些对元素属性获取或者转义一些选择器,完成一些对元素节点的排序等等。我会对这些方法进行一个注释与讲解,等把所有的这些辅助方法讲完后,会对sizzle的是怎么样选择到元素的一个流程,还有架构做一个梳理。解读方法的顺序基本是按照代码的先后顺序来讲。如果有误,请大家纠正。

matches

对已有元素进行筛选

/**
 * [matches] 对已有元素进行筛选
 * @param  {[type]} expr    
 * @param  {[type]} elements
 * @return Sizzle elements
 */
Sizzle.matches = function(expr, elements) {
     //selector, context, results, seed,这个就是种子元素中来过滤查找元素,不用在上下文中重新来查找
     return Sizzle(expr, null, null, elements);
};

Sizzle.matchesSelector

查看当前元素含有的是否包含这个选择器

/**
 * [matchesSelector ] 查看当前元素含有的是否包含这个选择
 * @param  {[element]} elem 
 * @param  {[string]} expr 
 * @return {[boolean]}    true or false
 */
Sizzle.matchesSelector = function(elem, expr) {
        // 设置当前的上下文的兼容性
        setDocument(elem);
        /**
         * support.matchesSelector 是否支持
         * documentIsHTML 是不是html
         * !nonnativeSelectorCache[expr + " "]  查看是否在缓存中为false
         * rbuggyMatches 不存在或!rbuggyMatches.test(expr) matches不存在bug
         * rbuggyQSA不存在或 就是QSA不存在bug 
         */
        if (support.matchesSelector && documentIsHTML &&
                !nonnativeSelectorCache[expr + " "] &&
                (!rbuggyMatches || !rbuggyMatches.test(expr)) &&
                (!rbuggyQSA || !rbuggyQSA.test(expr))) {

                try {
                        // 看是否能匹配上
                        var ret = matches.call(elem, expr);
                        // 如果选择器与元素匹配上 或者不是文档片段或者还在当前或者还在当前上下文
                        // IE 9's matchesSelector returns false on disconnected nodes
                        if (ret || support.disconnectedMatch ||

                                // As well, disconnected nodes are said to be in a document
                                // fragment in IE 9
                                elem.document && elem.document.nodeType !== 11) {
                                // 直接返回true
                                return ret;
                        }
                } catch (e) {
                        // 如果不能支持选择器放入 nonnativeSelectorCache
                        nonnativeSelectorCache(expr, true);
                }
        }
        // 走入catch就会走这里,重新进行元素的一个匹配,如果找道返回true
        return Sizzle(expr, document, null, [elem]).length > 0;
};

Sizzle.contains

来表示传入的节点是否为该节点的后代节点,返回true or false

/**
 * [contains]
 * @param  {[element]} context
 * @param  {[element]} elem   
 * @return {[boolean]}        
 */
Sizzle.contains = function(context, elem) {

        // Set document vars if needed
        // Support: IE 11+, Edge 17 - 18+
        // IE/Edge sometimes throw a "Permission denied" error when strict-comparing
        // two documents; shallow comparisons work.
        // eslint-disable-next-line eqeqeq
        // 根据文档来设置上下问
        if ((context.ownerDocument || context) != document) {
                setDocument(context);
        }
        // 来表示传入的节点是否为该节点的后代节点,返回true or false
        return contains(context, elem);
};

Sizzle.escape

转义css选择器,首先把选择器转为字符串,然后用正则匹配需要转义的字符串,替换成unicode字符。这两个方法我已经在前置内容中已经讲过了。大家没看的可以从前几篇文章中找一下呢

Sizzle.escape = function(sel) {
        return (sel + "").replace(rcssescape, fcssescape);
};

Sizzle.error

传参返回一个错误提示


Sizzle.error = function(msg) {
        throw new Error("Syntax error, unrecognized expression: " + msg);
};

Sizzle.uniqueSort

这个方法主要做的是给文档排序,上次节代码里面我已经讲到了怎么给文档排序了,不了的可以查看一下。还有一个做用就是删除重复的文档。我记得上节也讲到了sortOrder 这个方法了,如果a === b 相等时候 hasDuplicate 为true,说明文档有重复或者元素重复。

// Support: Chrome 14-35+ // Always assume duplicates if they aren't passed to the comparison function 加入没有传递这个参数就是一只重复着 support.detectDuplicates = !!hasDuplicate; 其实就是转了个boolean类型。这行代码在jquery的2921行。

// One-time assignments

// Sort stability 这个代码是对排序稳定性的一个测试, expando 是sizzle + 一个时间戳。 sortOrder = function( a, b ) { if ( a === b ) { hasDuplicate = true; } return 0; } // 就这块return 0 expando还是原来的顺序 hasDuplicate = true; support.sortStable = expando.split( "" ).sort( sortOrder ).join( "" ) === expando;

/**
 * Document sorting and removing duplicates
 * @param {ArrayLike} results
 */
Sizzle.uniqueSort = function(results) {
        var elem,
                duplicates = [],
                j = 0,
                i = 0;

        // Unless we *know* we can detect duplicates, assume their presence
        // Document的时候回执行sortOrder方法,如果支持hasCompare? hasDuplicate是true,否则是false
        hasDuplicate = !support.detectDuplicates;
        //sizzle1622222893304 就是false,&& results.slice(0) 把result 返回一个新数组浅拷贝
        sortInput = !support.sortStable && results.slice(0);
        然后对results进行排序
        results.sort(sortOrder);
        if (hasDuplicate) {
                while ((elem = results[i++])) {
                        if (elem === results[i]) {
                                j = duplicates.push(i);
                        }
                }
                // j 等于duplicates.length 存储的就是数字,duplicates存储的是重复的数字的位置
                while (j--) {
                        results.splice(duplicates[j], 1);
                }
        }

        // Clear input after sorting to release objects
        // See https://github.com/jquery/sizzle/pull/225
        sortInput = null;
        返回删剩下没有重复的元素
        return results;
};

Sizzle.getText

获取元素或者一组元素的文本内容

/**
 * Utility function for retrieving the text value of an array of DOM nodes
 * @param {Array|Element} elem
 */
 、、
getText = Sizzle.getText = function( elem ) {
	var node,
		ret = "",
		i = 0,
		nodeType = elem.nodeType;
        // 如果不存在nodeType属性,说明是类数组
	if ( !nodeType ) {

		// If no nodeType, this is expected to be an array
		while ( ( node = elem[ i++ ] ) ) {
                        // 递归拿到元素的内容
			// Do not traverse comment nodes
			ret += getText( node );
		}
                // 如果是元素或者document 或者 文档片段
	} else if ( nodeType === 1 || nodeType === 9 || nodeType === 11 ) {

		// Use textContent for elements
		// innerText usage removed for consistency of new lines (jQuery #11153)
                // 返回元素中文本内容
		if ( typeof elem.textContent === "string" ) {
			return elem.textContent;
		} else {
                        否则遍历递归每个兄弟节点拿到文本
			// Traverse its children
			for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) {
				ret += getText( elem );
			}
		}
                // 若果是CDATA节点或者文本节点 ,返回nodevalue
	} else if ( nodeType === 3 || nodeType === 4 ) {
		return elem.nodeValue;
	}

	// Do not include comment or processing instruction nodes

	return ret;
};

Sizzle.selectors

这个方法主要是绑定了 cacheLength、createPseudo、match、attrHandle、find、preFilter、filter、pseudos这个几个方法。

cacheLength 这个属性的作用是 方法createCache 的中keys的长度,如果超过keys的长度超过了50,让第一个缓存出队。

createPseudo:markFunction 这个方法默认是接收一个fn参数,给fn添加一个[expando] = true的属性。并返回这个fn。

matchExpr这个属性这个在分析一之时,我分析过了所以的属性,用来匹配各种选择器。 attrHandle: 空对象 Expr.attrHandle[arr[i]] = handler;

find: 空对象,在setDocument初始化的的时候赋予的对应的key:value,返回元素数组 不明白看看上一章

relative: 选择的相对位置

preFilter: 预过滤器这里起到一个预处理的作用,我举个例子preFilter["CHILD"]

  match:  [
        0: ":nth-child(2)"
        1: "nth"
        2: "child"
        3: "2"
        4: 0
        5: 2
        6: undefined
        7: ""
        8: "2"
        groups: undefined
        index: 0
        input: ":nth-child(2)"
        length: 9
   ]

这是一个被令牌化的一个选择器,我们把检测这个令牌画的选择器是否是合法的,怎么检测呢,首先检测 1: "nth"是否存在,然后检测 nth-child这个选择器是否有参数 比如说nth-child()没有参数,这肯定是一个报错的选择器,所以不行,3: "2" 有参数组合起来就是nth-child(2),然后查看match[4]是否存在。不存在2 * ( match[ 3 ] === "even" || match[ 3 ] === "odd" ) );存在match[ 5 ] + ( match[ 6 ] || 1 ),match[ 5 ] = +( ( match[ 7 ] + match[ 8 ] ) || match[ 3 ] === "odd" );对传入参数做处理。

filter:过滤器 在setDocument初始化的的时候赋予的对应的key:value,返回true or false

pseudos:伪类选择器

这块处理我会在下节继续接着解析css选择器,选中html元素结合着把这块来一个传插,并举一些实际的代码。 这块相对比较好理解,会举几个方法来讲一下。这块不理解可以先看,下章sizzle是怎么选择对这些选择器是如何处理的。

 Sizzle.selectors = {
	// Can be adjusted by the user
	cacheLength: 50,
	createPseudo: markFunction,
	match: matchExpr,

	attrHandle: {},

	find: {},
        //我们匹配选择器dir 这样处理 relative['dir'] ,通过这样来拿到这元素位置
	relative: {
		">": { dir: "parentNode", first: true },
		" ": { dir: "parentNode" },
		"+": { dir: "previousSibling", first: true },
		"~": { dir: "previousSibling" }
	},
        // 这块的preFilter做的处理,预先解析,生成的token做一个预判,
	preFilter: {
		"ATTR": function( match ) {
			match[ 1 ] = match[ 1 ].replace( runescape, funescape );

			// Move the given value to match[3] whether quoted or unquoted
			match[ 3 ] = ( match[ 3 ] || match[ 4 ] ||
				match[ 5 ] || "" ).replace( runescape, funescape );

			if ( match[ 2 ] === "~=" ) {
				match[ 3 ] = " " + match[ 3 ] + " ";
			}

			return match.slice( 0, 4 );
		},
                 /*
                  match:  [0: ":nth-child(2)"
                    1: "nth"
                    2: "child"
                    3: "2"
                    4: 0
                    5: 2
                    6: undefined
                    7: ""
                    8: "2"
                    groups: undefined
                    index: 0
                    input: ":nth-child(2)"
                    length: 9]
                 */
		"CHILD": function( match ) {
			/* matches from matchExpr["CHILD"]
				1 type (only|nth|...)
				2 what (child|of-type)
				3 argument (even|odd|\d*|\d*n([+-]\d+)?|...)
				4 xn-component of xn+y argument ([+-]?\d*n|)
				5 sign of xn-component
				6 x of xn-component
				7 sign of y-component
				8 y of y-component
			*/
                       // 拿到nth后代选择器
			match[ 1 ] = match[ 1 ].toLowerCase();
                      //  如果是nth 
			if ( match[ 1 ].slice( 0, 3 ) === "nth" ) {
                                // 判断后端选择的nth-child(arg) arg是否有参数or 0
				// nth-* requires argument
				if ( !match[ 3 ] ) {
					Sizzle.error( match[ 0 ] );
				}
                                
				// numeric x and y parameters for Expr.filter.CHILD
				// remember that false/true cast respectively to 0/1
				match[ 4 ] = +( match[ 4 ] ?
					match[ 5 ] + ( match[ 6 ] || 1 ) :
					2 * ( match[ 3 ] === "even" || match[ 3 ] === "odd" ) );
				match[ 5 ] = +( ( match[ 7 ] + match[ 8 ] ) || match[ 3 ] === "odd" );
                                 其他选择器报错
				// other types prohibit arguments
			} else if ( match[ 3 ] ) {
				Sizzle.error( match[ 0 ] );
			}
                         //返回组装后的租户在哪个结果
			return match;
		},

		"PSEUDO": function( match ) {
			var excess,
				unquoted = !match[ 6 ] && match[ 2 ];

			if ( matchExpr[ "CHILD" ].test( match[ 0 ] ) ) {
				return null;
			}

			// Accept quoted arguments as-is
			if ( match[ 3 ] ) {
				match[ 2 ] = match[ 4 ] || match[ 5 ] || "";

			// Strip excess characters from unquoted arguments
			} else if ( unquoted && rpseudo.test( unquoted ) &&

				// Get excess from tokenize (recursively)
				( excess = tokenize( unquoted, true ) ) &&

				// advance to the next closing parenthesis
				( excess = unquoted.indexOf( ")", unquoted.length - excess ) - unquoted.length ) ) {

				// excess is a negative index
				match[ 0 ] = match[ 0 ].slice( 0, excess );
				match[ 2 ] = unquoted.slice( 0, excess );
			}

			// Return only captures needed by the pseudo filter method (type and argument)
			return match.slice( 0, 3 );
		}
	},

	filter: {

		"TAG": function( nodeNameSelector ) {
			var nodeName = nodeNameSelector.replace( runescape, funescape ).toLowerCase();
			return nodeNameSelector === "*" ?
				function() {
					return true;
				} :
				function( elem ) {
					return elem.nodeName && elem.nodeName.toLowerCase() === nodeName;
				};
		},

		"CLASS": function( className ) {
			var pattern = classCache[ className + " " ];

			return pattern ||
				( pattern = new RegExp( "(^|" + whitespace +
					")" + className + "(" + whitespace + "|$)" ) ) && classCache(
						className, function( elem ) {
							return pattern.test(
								typeof elem.className === "string" && elem.className ||
								typeof elem.getAttribute !== "undefined" &&
									elem.getAttribute( "class" ) ||
								""
							);
				} );
		},

		"ATTR": function( name, operator, check ) {
			return function( elem ) {
				var result = Sizzle.attr( elem, name );

				if ( result == null ) {
					return operator === "!=";
				}
				if ( !operator ) {
					return true;
				}

				result += "";

				/* eslint-disable max-len */

				return operator === "=" ? result === check :
					operator === "!=" ? result !== check :
					operator === "^=" ? check && result.indexOf( check ) === 0 :
					operator === "*=" ? check && result.indexOf( check ) > -1 :
					operator === "$=" ? check && result.slice( -check.length ) === check :
					operator === "~=" ? ( " " + result.replace( rwhitespace, " " ) + " " ).indexOf( check ) > -1 :
					operator === "|=" ? result === check || result.slice( 0, check.length + 1 ) === check + "-" :
					false;
				/* eslint-enable max-len */

			};
		},

		"CHILD": function( type, what, _argument, first, last ) {
			var simple = type.slice( 0, 3 ) !== "nth",
				forward = type.slice( -4 ) !== "last",
				ofType = what === "of-type";

			return first === 1 && last === 0 ?

				// Shortcut for :nth-*(n)
				function( elem ) {
					return !!elem.parentNode;
				} :

				function( elem, _context, xml ) {
					var cache, uniqueCache, outerCache, node, nodeIndex, start,
						dir = simple !== forward ? "nextSibling" : "previousSibling",
						parent = elem.parentNode,
						name = ofType && elem.nodeName.toLowerCase(),
						useCache = !xml && !ofType,
						diff = false;

					if ( parent ) {

						// :(first|last|only)-(child|of-type)
						if ( simple ) {
							while ( dir ) {
								node = elem;
								while ( ( node = node[ dir ] ) ) {
									if ( ofType ?
										node.nodeName.toLowerCase() === name :
										node.nodeType === 1 ) {

										return false;
									}
								}

								// Reverse direction for :only-* (if we haven't yet done so)
								start = dir = type === "only" && !start && "nextSibling";
							}
							return true;
						}

						start = [ forward ? parent.firstChild : parent.lastChild ];

						// non-xml :nth-child(...) stores cache data on `parent`
						if ( forward && useCache ) {

							// Seek `elem` from a previously-cached index

							// ...in a gzip-friendly way
							node = parent;
							outerCache = node[ expando ] || ( node[ expando ] = {} );

							// Support: IE <9 only
							// Defend against cloned attroperties (jQuery gh-1709)
							uniqueCache = outerCache[ node.uniqueID ] ||
								( outerCache[ node.uniqueID ] = {} );

							cache = uniqueCache[ type ] || [];
							nodeIndex = cache[ 0 ] === dirruns && cache[ 1 ];
							diff = nodeIndex && cache[ 2 ];
							node = nodeIndex && parent.childNodes[ nodeIndex ];

							while ( ( node = ++nodeIndex && node && node[ dir ] ||

								// Fallback to seeking `elem` from the start
								( diff = nodeIndex = 0 ) || start.pop() ) ) {

								// When found, cache indexes on `parent` and break
								if ( node.nodeType === 1 && ++diff && node === elem ) {
									uniqueCache[ type ] = [ dirruns, nodeIndex, diff ];
									break;
								}
							}

						} else {

							// Use previously-cached element index if available
							if ( useCache ) {

								// ...in a gzip-friendly way
								node = elem;
								outerCache = node[ expando ] || ( node[ expando ] = {} );

								// Support: IE <9 only
								// Defend against cloned attroperties (jQuery gh-1709)
								uniqueCache = outerCache[ node.uniqueID ] ||
									( outerCache[ node.uniqueID ] = {} );

								cache = uniqueCache[ type ] || [];
								nodeIndex = cache[ 0 ] === dirruns && cache[ 1 ];
								diff = nodeIndex;
							}

							// xml :nth-child(...)
							// or :nth-last-child(...) or :nth(-last)?-of-type(...)
							if ( diff === false ) {

								// Use the same loop as above to seek `elem` from the start
								while ( ( node = ++nodeIndex && node && node[ dir ] ||
									( diff = nodeIndex = 0 ) || start.pop() ) ) {

									if ( ( ofType ?
										node.nodeName.toLowerCase() === name :
										node.nodeType === 1 ) &&
										++diff ) {

										// Cache the index of each encountered element
										if ( useCache ) {
											outerCache = node[ expando ] ||
												( node[ expando ] = {} );

											// Support: IE <9 only
											// Defend against cloned attroperties (jQuery gh-1709)
											uniqueCache = outerCache[ node.uniqueID ] ||
												( outerCache[ node.uniqueID ] = {} );

											uniqueCache[ type ] = [ dirruns, diff ];
										}

										if ( node === elem ) {
											break;
										}
									}
								}
							}
						}

						// Incorporate the offset, then check against cycle size
						diff -= last;
						return diff === first || ( diff % first === 0 && diff / first >= 0 );
					}
				};
		},

		"PSEUDO": function( pseudo, argument ) {

			// pseudo-class names are case-insensitive
			// http://www.w3.org/TR/selectors/#pseudo-classes
			// Prioritize by case sensitivity in case custom pseudos are added with uppercase letters
			// Remember that setFilters inherits from pseudos
			var args,
				fn = Expr.pseudos[ pseudo ] || Expr.setFilters[ pseudo.toLowerCase() ] ||
					Sizzle.error( "unsupported pseudo: " + pseudo );

			// The user may use createPseudo to indicate that
			// arguments are needed to create the filter function
			// just as Sizzle does
			if ( fn[ expando ] ) {
				return fn( argument );
			}

			// But maintain support for old signatures
			if ( fn.length > 1 ) {
				args = [ pseudo, pseudo, "", argument ];
				return Expr.setFilters.hasOwnProperty( pseudo.toLowerCase() ) ?
					markFunction( function( seed, matches ) {
						var idx,
							matched = fn( seed, argument ),
							i = matched.length;
						while ( i-- ) {
							idx = indexOf( seed, matched[ i ] );
							seed[ idx ] = !( matches[ idx ] = matched[ i ] );
						}
					} ) :
					function( elem ) {
						return fn( elem, 0, args );
					};
			}

			return fn;
		}
	},

	pseudos: {

		// Potentially complex pseudos
		"not": markFunction( function( selector ) {

			// Trim the selector passed to compile
			// to avoid treating leading and trailing
			// spaces as combinators
			var input = [],
				results = [],
				matcher = compile( selector.replace( rtrim, "$1" ) );

			return matcher[ expando ] ?
				markFunction( function( seed, matches, _context, xml ) {
					var elem,
						unmatched = matcher( seed, null, xml, [] ),
						i = seed.length;

					// Match elements unmatched by `matcher`
					while ( i-- ) {
						if ( ( elem = unmatched[ i ] ) ) {
							seed[ i ] = !( matches[ i ] = elem );
						}
					}
				} ) :
				function( elem, _context, xml ) {
					input[ 0 ] = elem;
					matcher( input, null, xml, results );

					// Don't keep the element (issue #299)
					input[ 0 ] = null;
					return !results.pop();
				};
		} ),

		"has": markFunction( function( selector ) {
			return function( elem ) {
				return Sizzle( selector, elem ).length > 0;
			};
		} ),

		"contains": markFunction( function( text ) {
			text = text.replace( runescape, funescape );
			return function( elem ) {
				return ( elem.textContent || getText( elem ) ).indexOf( text ) > -1;
			};
		} ),

		// "Whether an element is represented by a :lang() selector
		// is based solely on the element's language value
		// being equal to the identifier C,
		// or beginning with the identifier C immediately followed by "-".
		// The matching of C against the element's language value is performed case-insensitively.
		// The identifier C does not have to be a valid language name."
		// http://www.w3.org/TR/selectors/#lang-pseudo
		"lang": markFunction( function( lang ) {

			// lang value must be a valid identifier
			if ( !ridentifier.test( lang || "" ) ) {
				Sizzle.error( "unsupported lang: " + lang );
			}
			lang = lang.replace( runescape, funescape ).toLowerCase();
			return function( elem ) {
				var elemLang;
				do {
					if ( ( elemLang = documentIsHTML ?
						elem.lang :
						elem.getAttribute( "xml:lang" ) || elem.getAttribute( "lang" ) ) ) {

						elemLang = elemLang.toLowerCase();
						return elemLang === lang || elemLang.indexOf( lang + "-" ) === 0;
					}
				} while ( ( elem = elem.parentNode ) && elem.nodeType === 1 );
				return false;
			};
		} ),

		// Miscellaneous
		"target": function( elem ) {
			var hash = window.location && window.location.hash;
			return hash && hash.slice( 1 ) === elem.id;
		},

		"root": function( elem ) {
			return elem === docElem;
		},

		"focus": function( elem ) {
			return elem === document.activeElement &&
				( !document.hasFocus || document.hasFocus() ) &&
				!!( elem.type || elem.href || ~elem.tabIndex );
		},

		// Boolean properties
		"enabled": createDisabledPseudo( false ),
		"disabled": createDisabledPseudo( true ),

		"checked": function( elem ) {

			// In CSS3, :checked should return both checked and selected elements
			// http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked
			var nodeName = elem.nodeName.toLowerCase();
			return ( nodeName === "input" && !!elem.checked ) ||
				( nodeName === "option" && !!elem.selected );
		},

		"selected": function( elem ) {

			// Accessing this property makes selected-by-default
			// options in Safari work properly
			if ( elem.parentNode ) {
				// eslint-disable-next-line no-unused-expressions
				elem.parentNode.selectedIndex;
			}

			return elem.selected === true;
		},

		// Contents
		"empty": function( elem ) {

			// http://www.w3.org/TR/selectors/#empty-pseudo
			// :empty is negated by element (1) or content nodes (text: 3; cdata: 4; entity ref: 5),
			//   but not by others (comment: 8; processing instruction: 7; etc.)
			// nodeType < 6 works because attributes (2) do not appear as children
			for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) {
				if ( elem.nodeType < 6 ) {
					return false;
				}
			}
			return true;
		},

		"parent": function( elem ) {
			return !Expr.pseudos[ "empty" ]( elem );
		},

		// Element/input types
		"header": function( elem ) {
			return rheader.test( elem.nodeName );
		},

		"input": function( elem ) {
			return rinputs.test( elem.nodeName );
		},

		"button": function( elem ) {
			var name = elem.nodeName.toLowerCase();
			return name === "input" && elem.type === "button" || name === "button";
		},

		"text": function( elem ) {
			var attr;
			return elem.nodeName.toLowerCase() === "input" &&
				elem.type === "text" &&

				// Support: IE<8
				// New HTML5 attribute values (e.g., "search") appear with elem.type === "text"
				( ( attr = elem.getAttribute( "type" ) ) == null ||
					attr.toLowerCase() === "text" );
		},

		// Position-in-collection
		"first": createPositionalPseudo( function() {
			return [ 0 ];
		} ),

		"last": createPositionalPseudo( function( _matchIndexes, length ) {
			return [ length - 1 ];
		} ),

		"eq": createPositionalPseudo( function( _matchIndexes, length, argument ) {
			return [ argument < 0 ? argument + length : argument ];
		} ),

		"even": createPositionalPseudo( function( matchIndexes, length ) {
			var i = 0;
			for ( ; i < length; i += 2 ) {
				matchIndexes.push( i );
			}
			return matchIndexes;
		} ),

		"odd": createPositionalPseudo( function( matchIndexes, length ) {
			var i = 1;
			for ( ; i < length; i += 2 ) {
				matchIndexes.push( i );
			}
			return matchIndexes;
		} ),

		"lt": createPositionalPseudo( function( matchIndexes, length, argument ) {
			var i = argument < 0 ?
				argument + length :
				argument > length ?
					length :
					argument;
			for ( ; --i >= 0; ) {
				matchIndexes.push( i );
			}
			return matchIndexes;
		} ),

		"gt": createPositionalPseudo( function( matchIndexes, length, argument ) {
			var i = argument < 0 ? argument + length : argument;
			for ( ; ++i < length; ) {
				matchIndexes.push( i );
			}
			return matchIndexes;
		} )
	}
};

Sizzle源码分析(一) 基本概念

Sizzle源码分析(二) 工具方法

Sizzle源码分析(三) 兼容处理

Sizzle源码分析(四) sizzle静态方法分析