jquery 源码分析十二 - Sizzle
在Sizzle中只剩下最后一段大的函数了,就是select,也就是在sizzle中联系compile和Sizzle函数的关键。它在Sizzle中主要处理那些并非是简单的选择器,如包含有:eq(1)的。
直接看源码:
function select( selector, context, results, seed ) { var i, tokens, token, type, find, match = tokenize( selector ); if ( !seed ) { // 如果只有一个group的话,就减少分析,直接用以下的方法 if ( match.length === 1 ) { // 如果第一个选择符是ID的话,就直接取出来当做上下文 tokens = match[0] = match[0].slice( 0 ); if ( tokens.length > 2 && (token = tokens[0]).type === "ID" && support.getById && context.nodeType === 9 && documentIsHTML && Expr.relative[ tokens[1].type ] ) { // 返回context下的特定ID的元素 context = ( Expr.find["ID"]( token.matches[0].replace(runescape, funescape), context ) || [] )[0]; if ( !context ) { return results; } selector = selector.slice( tokens.shift().value.length ); } // 如果有:eq(1)或者>出现在开头就从左往右,否则就是从右到左 i = matchExpr["needsContext"].test( selector ) ? 0 : tokens.length; while ( i-- ) { token = tokens[i]; // 如果有' ','>','+','~',出现就直接break if ( Expr.relative[ (type = token.type) ] ) { break; } if ( (find = Expr.find[ type ]) ) { // 通过寻找相邻元素来扩展context if ( (seed = find( token.matches[0].replace( runescape, funescape ), rsibling.test( tokens[0].type ) && testContext( context.parentNode ) || context )) ) { // 如果seed没有或者tokens里没有东西了,就可以直接返回了 tokens.splice( i, 1 ); selector = seed.length && toSelector( tokens ); if ( !selector ) { push.apply( results, seed ); return results; } break; } } } } } // 编译并执行筛选函数 // 提供match是为了防止再次执行tokenize(在我们已经对match更改过的情况下) compile( selector, match )( seed, context, !documentIsHTML, results, rsibling.test( selector ) && testContext( context.parentNode ) || context ); return results; }
可以看到对于简单的情况,select更改了下context,节省后面产生筛选函数后的闭包深度。对于真正的筛选工作,还是有compile编译得到的超级选择函数来做的。
到这里,Sizzle里所有的大块函数基本都已经讲过了,下面就是在一个选择器传入后函数的运行步骤了。
=======================================================================================
首先是Sizzle暴露到外面的方法,几个主要的方法都在Sizzle闭包结束后运行,源码如下:
jQuery.find = Sizzle; jQuery.expr = Sizzle.selectors; jQuery.expr[":"] = jQuery.expr.pseudos; jQuery.unique = Sizzle.uniqueSort; jQuery.text = Sizzle.getText; jQuery.isXMLDoc = Sizzle.isXML; jQuery.contains = Sizzle.contains;
可以看到,jQuery.find完全是调用Sizzle来实现的,那么下面是普通的$('...')和$('...').find('...')形式,我们就要先重温下jQuery.fn.init中的代码片段了
// HANDLE: $(#id)
// rootjQuery = jQuery( document );
} else { elem = document.getElementById( match[2] ); // Check parentNode to catch when Blackberry 4.6 returns // nodes that are no longer in the document #6963 if ( elem && elem.parentNode ) { // Handle the case where IE and Opera return items // by name instead of ID if ( elem.id !== match[2] ) { return rootjQuery.find( selector ); } // Otherwise, we inject the element directly into the jQuery object this.length = 1; this[0] = elem; } this.context = document; this.selector = selector; return this; }
// HANDLE: $(expr, $(...)) } else if ( !context || context.jquery ) { return ( context || rootjQuery ).find( selector ); // HANDLE: $(expr, context) // (which is just equivalent to: $(context).find(expr) } else { return this.constructor( context ).find( selector ); }
可以看到,对于选择器的处理,基本上除了ID以外,都使用了$(context).find(selector)形式,我们跟踪到find函数中(注意:此时的find函数是指在prototype中定义的find函数,而不是jQuery.find,两者所处的位置是有区别的),直接上find源码:
find: function( selector ) { var i, ret = [], self = this, len = self.length; if ( typeof selector !== "string" ) { return this.pushStack( jQuery( selector ).filter(function() { for ( i = 0; i < len; i++ ) { if ( jQuery.contains( self[ i ], this ) ) { return true; } } }) ); } for ( i = 0; i < len; i++ ) { jQuery.find( selector, self[ i ], ret ); } ret = this.pushStack( len > 1 ? jQuery.unique( ret ) : ret ); ret.selector = this.selector ? this.selector + " " + selector : selector; return ret; },
在这个函数中,对于每一个context,都会执行一遍jQuery.find,即Sizzle,保证结果的齐全,同时在后面调用jQuery.unique来保证不重复。
接下来就是进入Sizzle主函数了,Sizzle主函数在之前已经分析过了,就不具体讲了,主要的还是简单的选择器的话就直接处理返回,复杂的话就会调用select函数
function Sizzle( selector, context, results, seed ) { var match, elem, m, nodeType, // QSA vars i, groups, old, nid, newContext, newSelector; if ( ( context ? context.ownerDocument || context : preferredDoc ) !== document ) { setDocument( context ); } context = context || document; results = results || []; if ( !selector || typeof selector !== "string" ) { return results; } if ( (nodeType = context.nodeType) !== 1 && nodeType !== 9 ) { return []; } if ( documentIsHTML && !seed ) { // Shortcuts if ( (match = rquickExpr.exec( selector )) ) { // Speed-up: Sizzle("#ID") if ( (m = match[1]) ) { if ( nodeType === 9 ) { elem = context.getElementById( m ); // Check parentNode to catch when Blackberry 4.6 returns // nodes that are no longer in the document (jQuery #6963) if ( elem && elem.parentNode ) { // Handle the case where IE, Opera, and Webkit return items // by name instead of ID if ( elem.id === m ) { results.push( elem ); return results; } } else { return results; } } else { // Context is not a document if ( context.ownerDocument && (elem = context.ownerDocument.getElementById( m )) && contains( context, elem ) && elem.id === m ) { results.push( elem ); return results; } } // Speed-up: Sizzle("TAG") } else if ( match[2] ) { push.apply( results, context.getElementsByTagName( selector ) ); return results; // Speed-up: Sizzle(".CLASS") } else if ( (m = match[3]) && support.getElementsByClassName && context.getElementsByClassName ) { push.apply( results, context.getElementsByClassName( m ) ); return results; } } // QSA path if ( support.qsa && (!rbuggyQSA || !rbuggyQSA.test( selector )) ) { nid = old = expando; newContext = context; newSelector = nodeType === 9 && selector; // qSA works strangely on Element-rooted queries // We can work around this by specifying an extra ID on the root // and working up from there (Thanks to Andrew Dupont for the technique) // IE 8 doesn't work on object elements 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 ) && testContext( 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"); } } } } } // All others return select( selector.replace( rtrim, "$1" ), context, results, seed ); }
select函数就对传入的selector进行简单的处理,再调用compile函数根据传入的selector和group来生成终极匹配函数,并立刻运行,来获取结果。
这些具体的函数运行步骤的话,还是自己做个demo单步跟踪下比较清晰
好了,jQuery中的Sizzle基本就这写了,剩下的一些小函数随便找点时间看下就懂了,都不讲了,后面的博客就会回到大的jQuery中了