jQuery選擇器探究:TAG選擇器和CLASS選擇器
阿新 • • 發佈:2019-01-01
(jquery1.6.1版本)假設有一個html文件中存在多個<h1>標籤元素,那麼當我們敲入$("h1")後在jQuery內部將會執行怎樣的邏輯呢?
分析jQuery建構函式我們同樣定位到find( selector )方法,這個方法是jQuery的例項方法,程式碼如下:(5137行)
這時需要定位到Sizzle的建構函式,這時問題來了,Sizzle的定義不止一處,一處(3706行)定義是通用的,也是傳統的,邏輯較為複雜,一處(4807行)定義在document.querySelectorAll存在時過載並複用傳統定義。本文分析的是過載的相對簡單的Sizzle定義。
當分組一存在時,執行tag選擇器邏輯;當分組二存在時,執行class選擇器邏輯。可以看到,對於這兩種選擇符,背後的邏輯回到原生dom的基本api上了:context.getElementsByTagName( query )、context.getElementsByClassName( match[2] ),當然,就像id選擇符一樣,Sizzle返回的物件集合肯定也不會是原始dom元素集合,所以makeArray函式的功能就是把選擇出來的原生dom元素集合inject到jQuery物件extra(第三個引數)中去:(4561行)
簡單的單純的TAG選擇器和CLASS選擇器執行邏輯就是這樣的,相對而言還是比較簡單的,後續分析複雜的組合的選擇器執行邏輯,也即真正的Sizzle核心。
分析jQuery建構函式我們同樣定位到find( selector )方法,這個方法是jQuery的例項方法,程式碼如下:(5137行)
我們暫不考慮其他呼叫的場景,只專注於selector引數為string型別時的邏輯。整體上呼叫流程是遵循jQuery一致的風格:例項方法"find(selector)"呼叫類方法"jQuery.find( selector, this[i], ret );",類方法find直接指向Sizzle建構函式"jQuery.find = Sizzle;"。當然還有一些其他的邏輯,如返回物件的構造、去重過濾等。find: function( selector ) { var self = this, i, l; if ( typeof selector !== "string" ) { return jQuery( selector ).filter(function() { for ( i = 0, l = self.length; i < l; i++ ) { if ( jQuery.contains( self[ i ], this ) ) { return true; } } }); } var ret = this.pushStack( "", "find", selector ), length, n, r; for ( i = 0, l = this.length; i < l; i++ ) { length = ret.length; jQuery.find( selector, this[i], ret ); if ( i > 0 ) { // Make sure that the results are unique for ( n = length; n < ret.length; n++ ) { for ( r = 0; r < length; r++ ) { if ( ret[r] === ret[n] ) { ret.splice(n--, 1); break; } } } } } return ret; },
這時需要定位到Sizzle的建構函式,這時問題來了,Sizzle的定義不止一處,一處(3706行)定義是通用的,也是傳統的,邏輯較為複雜,一處(4807行)定義在document.querySelectorAll存在時過載並複用傳統定義。本文分析的是過載的相對簡單的Sizzle定義。
在傳遞給Sizzle函式的四個引數( query, context, extra, seed )中,當context引數和seed引數滿足條件"if ( !seed && !Sizzle.isXML(context) )"時將執行過載的Sizzle邏輯,否則執行傳統Sizzle邏輯。這裡同樣用一個正則表示式物件"/^(\w+$)|^\.([\w\-]+$)|^#([\w\-]+$)/"對選擇符引數query執行匹配,這個正則物件有三個分組,分別捕獲tag/class/id,也就是說過載的Sizzle函式功能上支援這三種最基本的選擇器(儘管實際中#id選擇符在jQuery建構函式中已經處理過了,理論上走不到這個地方來)。Sizzle = function( query, context, extra, seed ) { context = context || document; // Only use querySelectorAll on non-XML documents // (ID selectors don't work in non-HTML documents) if ( !seed && !Sizzle.isXML(context) ) { // See if we find a selector to speed up var match = /^(\w+$)|^\.([\w\-]+$)|^#([\w\-]+$)/.exec( query ); if ( match && (context.nodeType === 1 || context.nodeType === 9) ) { // Speed-up: Sizzle("TAG") if ( match[1] ) { return makeArray( context.getElementsByTagName( query ), extra ); // Speed-up: Sizzle(".CLASS") } else if ( match[2] && Expr.find.CLASS && context.getElementsByClassName ) { return makeArray( context.getElementsByClassName( match[2] ), extra ); } } if ( context.nodeType === 9 ) { // Speed-up: Sizzle("body") // The body element only exists once, optimize finding it if ( query === "body" && context.body ) { return makeArray( [ context.body ], extra ); // Speed-up: Sizzle("#ID") } else if ( match && match[3] ) { var elem = context.getElementById( match[3] ); // 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[3] ) { return makeArray( [ elem ], extra ); } } else { return makeArray( [], extra ); } } try { return makeArray( context.querySelectorAll(query), extra ); } catch(qsaError) {} // 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 } else if ( context.nodeType === 1 && context.nodeName.toLowerCase() !== "object" ) { var oldContext = context, old = context.getAttribute( "id" ), nid = old || id, hasParent = context.parentNode, relativeHierarchySelector = /^\s*[+~]/.test( query ); if ( !old ) { context.setAttribute( "id", nid ); } else { nid = nid.replace( /'/g, "\\$&" ); } if ( relativeHierarchySelector && hasParent ) { context = context.parentNode; } try { if ( !relativeHierarchySelector || hasParent ) { return makeArray( context.querySelectorAll( "[id='" + nid + "'] " + query ), extra ); } } catch(pseudoError) { } finally { if ( !old ) { oldContext.removeAttribute( "id" ); } } } } return oldSizzle(query, context, extra, seed); };
當分組一存在時,執行tag選擇器邏輯;當分組二存在時,執行class選擇器邏輯。可以看到,對於這兩種選擇符,背後的邏輯回到原生dom的基本api上了:context.getElementsByTagName( query )、context.getElementsByClassName( match[2] ),當然,就像id選擇符一樣,Sizzle返回的物件集合肯定也不會是原始dom元素集合,所以makeArray函式的功能就是把選擇出來的原生dom元素集合inject到jQuery物件extra(第三個引數)中去:(4561行)
makeArray函式的邏輯比較簡單:接受兩個引數,第一個是待處理array like物件,第二個是待擴充套件物件,不傳遞第二個物件時,直接將array like物件轉換為真正的array物件,否則將轉換後的真正的array物件inject到第二個引數jQuery物件中去。var makeArray = function( array, results ) { array = Array.prototype.slice.call( array, 0 ); if ( results ) { results.push.apply( results, array ); return results; } return array; };
簡單的單純的TAG選擇器和CLASS選擇器執行邏輯就是這樣的,相對而言還是比較簡單的,後續分析複雜的組合的選擇器執行邏輯,也即真正的Sizzle核心。