jQuery使用的是sizzle這個選擇器引擎,這個引擎以其高速著稱,其實現十分精妙可是也足夠複雜,下面現簡單分析一下相關的代碼。node
在jQuery的部分API接口是直接引用了Sizzle的方法,這些接口以下:web
1 jQuery.find = Sizzle; 2 jQuery.expr = Sizzle.selectors; 3 jQuery.expr[":"] = jQuery.expr.pseudos; 4 jQuery.unique = Sizzle.uniqueSort; 5 jQuery.text = Sizzle.getText; 6 jQuery.isXMLDoc = Sizzle.isXML; 7 jQuery.contains = Sizzle.contains;
1 // @param selector 已去掉頭尾空白的選擇器字符串 2 // @param context 執行匹配的最初的上下文(即DOM元素集合)。若context沒有賦值,則取document。 3 // @param results 已匹配出的部分最終結果。若results沒有賦值,則賦予空數組。 4 // @param seed 初始集合 5 6 function Sizzle( selector, context, results, seed ) { 7 var match, elem, m, nodeType, 8 // QSA vars 9 i, groups, old, nid, newContext, newSelector; 10 if ( ( context ? context.ownerDocument || context : preferredDoc ) !== document ) { 11 12 // 根據不一樣的瀏覽器環境,設置合適的Expr方法,構造合適的rbuggy測試 13 setDocument( context ); 14 } 15 context = context || document; 16 results = results || []; 17 nodeType = context.nodeType; 18 if ( typeof selector !== "string" || !selector || 19 nodeType !== 1 && nodeType !== 9 && nodeType !== 11 ) { 20 return results; 21 } 22 if ( !seed && documentIsHTML ) { 23 // 儘量快地找到目標節點, 選擇器類型是id,標籤和類 24 // rquickExpr = /^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/ 25 // 將selector按 #[id] / [tag] / .[class]的順序捕獲到數組中,數組的第一個元素是原始值 26 // 捕獲結果中'#'和'.'會被移除 27 if ( nodeType !== 11 && (match = rquickExpr.exec( selector )) ) { 28 // 加速: Sizzle("#ID") 29 if ( (m = match[1]) ) { 30 if ( nodeType === 9 ) { 31 elem = context.getElementById( m ); 32 // 檢查Blackberry 4.6返回的已經不在document中的parentNode 33 if ( elem && elem.parentNode ) { 34 // IE, Opera, Webkit有時候會返回name == m的元素 35 if ( elem.id === m ) { 36 results.push( elem ); 37 return results; 38 } 39 } else { 40 return results; 41 } 42 } else { 43 // 上下文不是document 44 if ( context.ownerDocument && 45 46 (elem = context.ownerDocument.getElementById( m )) && 47 contains( context, elem ) && elem.id === m ) { 48 49 results.push( elem ); 50 return results; 51 } 52 } 53 // 加速: Sizzle("TAG") 54 // 因爲返回是一個數組,所以須要讓這個數組做爲參數數組並利用push.apply調用將其拼接到results後面 55 } else if ( match[2] ) { 56 push.apply( results, context.getElementsByTagName( selector ) ); 57 return results; 58 // 加速: Sizzle(".CLASS") 59 // push.apply的使用緣由同上 60 } else if ( (m = match[3]) && support.getElementsByClassName ) { 61 push.apply( results, context.getElementsByClassName( m ) ); 62 return results; 63 } 64 } 65 66 // 使用QSA, QSA: querySelectorAll, 原生的QSA運行速度很是快,所以儘量使用QSA來對CSS選擇器進行查詢 67 // querySelectorAll是原生的選擇器,但不支持老的瀏覽器版本, 主要是IE8及之前的瀏覽器 68 // rbuggyQSA 保存了用於解決一些瀏覽器兼容問題的bug修補的正則表達式 69 // QSA在不一樣瀏覽器上運行的效果有差別,表現得很是奇怪,所以對某些selector不能用QSA 70 // 爲了適應不一樣的瀏覽器,就須要首先進行瀏覽器兼容性測試,而後肯定測試正則表達式,用rbuggyQSA來肯定selector是否能用QSA 71 72 if ( support.qsa && (!rbuggyQSA || !rbuggyQSA.test( selector )) ) { 73 nid = old = expando; 74 newContext = context; 75 newSelector = nodeType !== 1 && selector; 76 77 // QSA 在以某個根節點ID爲基礎的查找中(.rootClass span)表現很奇怪, 78 // 它會忽略某些selector選項,返回不合適的結果 79 // 一個比較一般的解決方法是爲根節點設置一個額外的id,並以此開始查詢 80 // IE 8 doesn't work on object elements 81 if ( nodeType === 1 && context.nodeName.toLowerCase() !== "object" ) { 82 groups = tokenize( selector ); // 分析選擇器的詞法並返回一個詞法標記數組 83 if ( (old = context.getAttribute("id")) ) { // 保存並設置新id 84 nid = old.replace( rescape, "\\$&" ); 85 } else { 86 context.setAttribute( "id", nid ); 87 } 88 nid = "[id='" + nid + "'] "; 89 i = groups.length; 90 while ( i-- ) { 91 groups[i] = nid + toSelector( groups[i] ); // 把新的id添加到選擇器標記裏 92 } 93 newContext = rsibling.test( selector ) && testContext( context.parentNode ) || context; 94 newSelector = groups.join(","); // 構造新的選擇器 95 } 96 if ( newSelector ) { // 使用新的選擇器經過QSA來查詢元素 97 try { 98 push.apply( results, // 將查詢結果合併到results上 99 newContext.querySelectorAll( newSelector ) 100 ); 101 return results; 102 } catch(qsaError) { 103 } finally { 104 if ( !old ) { 105 context.removeAttribute("id"); // 若是沒有舊id,則移除 106 } 107 } 108 } 109 } 110 } 111 // 其餘selector,這些selector沒法直接使用原生的document查詢方法 112 return select( selector.replace( rtrim, "$1" ), context, results, seed ); 113 }
1 /** 2 * Support testing using an element 3 * @param {Function} fn Passed the created div and expects a boolean result 4 */ 5 function assert( fn ) { 6 var div = document.createElement("div"); // 建立測試用節點 7 try { 8 return !!fn( div ); // 轉換fn的返回值爲boolean值 9 } catch (e) { 10 return false; 11 } finally { 12 if ( div.parentNode ) { // 結束時移除這個節點 13 div.parentNode.removeChild( div ); 14 } 15 div = null; // IE瀏覽器中必須這樣,釋放內存 16 } 17 }
1 assert(function( div ) { 2 3 // 建立一些子節點 4 docElem.appendChild( div ).innerHTML = "<a id='" + expando + "'></a>" + 5 "<select id='" + expando + "-\f]' msallowcapture=''>" + 6 "<option selected=''></option></select>"; 7 ... // 其餘測試 8 // 測試document.querySelectorAll()的正確性 9 if ( div.querySelectorAll("[msallowcapture^='']").length ) { 10 rbuggyQSA.push( "[*^$]=" + whitespace + "*(?:''|\"\")" ); 11 12 // 肯定用於測試selector可否使用QSA的正則表達式 13 14 } 15 ... // 其餘測試 16 });
1 // @param selector 已去掉頭尾空白的選擇器字符串 2 // @param context 執行匹配的最初的上下文(即DOM元素集合)。若context沒有賦值,則取document。 3 // @param results 已匹配出的部分最終結果。若results沒有賦值,則賦予空數組。 4 // @param seed 初始集合 5 6 select = Sizzle.select = function( selector, context, results, seed ) { 7 var i, tokens, token, type, find, 8 compiled = typeof selector === "function" && selector, 9 match = !seed && tokenize( (selector = compiled.selector || selector) ); 10 results = results || []; 11 // 當沒有seed或group時,儘量地減小操做 12 if ( match.length === 1 ) { 13 14 // 若是根選擇器是id,利用快捷方式並設置context 15 tokens = match[0] = match[0].slice( 0 ); 16 if ( tokens.length > 2 && (token = tokens[0]).type === "ID" && 17 support.getById && context.nodeType === 9 && documentIsHTML && 18 Expr.relative[ tokens[1].type ] ) { 19 20 // 使用Expr.find["ID"]查找元素,其中調用了context.getElementById方法 21 // 爲了兼容不一樣的瀏覽器,setDocument方法會測試不一樣的瀏覽器環境並構造一個使用與當前運行環境的Expr.find["ID"]元素 22 // 將id選擇器的返回結果做爲新的上下文 23 24 context = ( Expr.find["ID"]( token.matches[0].replace(runescape, funescape), context ) || [] )[0]; 25 if ( !context ) { 26 return results; // 若是找不到id根元素直接返回results 27 // Precompiled matchers will still verify ancestry, so step up a level 28 } else if ( compiled ) { 29 context = context.parentNode; 30 } 31 // 移除第一個id選擇器 32 selector = selector.slice( tokens.shift().value.length ); 33 } 34 // Fetch a seed set for right-to-left matching 35 // matchExpr["needsContext"]測試選擇器是否含有位置僞類,如:first,:even,或包含"> + ~"等關係 36 37 // 若是包含將i賦值0,不然賦值tokens.length 38 i = matchExpr["needsContext"].test( selector ) ? 0 : tokens.length; 39 40 // 遍歷tokens, 逐個查詢 41 while ( i-- ) { 42 token = tokens[i]; 43 // 遇到關係符"~ + > ."的時候跳出 44 45 if ( Expr.relative[ (type = token.type) ] ) { 46 break; 47 } 48 49 // 根據type獲取查詢方法 50 if ( (find = Expr.find[ type ]) ) { 51 // Search, expanding context for leading sibling combinators 52 // rsibling = /[+~]/, 用於判斷同胞關係符 53 if ( (seed = find( 54 token.matches[0].replace( runescape, funescape ), 55 rsibling.test( tokens[0].type ) && testContext( context.parentNode ) || context 56 )) ) { 57 58 // 若是seed是空的或者沒有任何token了,就能夠提早返回 59 // 不然,就根據新的seed和token,迭代地繼續搜索下去 60 tokens.splice( i, 1 ); 61 selector = seed.length && toSelector( tokens ); 62 if ( !selector ) { 63 push.apply( results, seed ); 64 return results; 65 } 66 break; 67 } 68 } 69 } 70 } 71 // Compile and execute a filtering function if one is not provided 72 // Provide `match` to avoid retokenization if we modified the selector above 73 // 執行compile返回一個匹配器函數, 再利用這個返回的函數進行匹配; 74 ( compiled || compile( selector, match ) )( 75 seed, 76 context, 77 !documentIsHTML, 78 results, 79 rsibling.test( selector ) && testContext( context.parentNode ) || context 80 ); 81 return results; 82 };
1 compile = Sizzle.compile = function( selector, match /* Internal Use Only */ ) { 2 var i, 3 setMatchers = [], 4 elementMatchers = [], 5 cached = compilerCache[ selector + " " ]; // 根據selector獲取cache中的匹配器 6 // 若是還沒有建立這個匹配器,則須要建立一個 7 if ( !cached ) { 8 // 產生一個函數,這個函數包含一系列遞歸函數用來檢索每個元素 9 if ( !match ) { 10 match = tokenize( selector ); // 解析選擇器詞法 11 } 12 i = match.length; 13 while ( i-- ) { 14 cached = matcherFromTokens( match[i] ); // 根據token建立匹配器 15 if ( cached[ expando ] ) { 16 setMatchers.push( cached ); 17 } else { 18 elementMatchers.push( cached ); 19 } 20 } 21 // Cache the compiled function 22 // matcherFromGroupMatchers 返回一個superMatcher 23 // compelerCache = createCache(), 根據selector創建匹配器方法cache 24 cached = compilerCache( selector, matcherFromGroupMatchers( elementMatchers, setMatchers ) ); 25 //在cached中保存選擇器 26 cached.selector = selector; 27 } 28 29 // 返回這個對應的匹配器 30 return cached; 31 };