2.1 穩定 版本
Sizzle 選擇器引擎博大精深,下面開始閱讀它的源代碼,並從中作出標記 。先從入口開始,以後慢慢切入 。javascript
源碼 194-301 行java
function Sizzle(selector, context, results, seed) { var match, elem, m, nodeType, i, groups, old, nid, newContext, newSelector; //37 行, preferredDoc = window.document, // 這裏防止document被重寫。 // 若是了上下文對象的ownerDocument || preferredDoc 不等於document,覆寫全部涉及到document的內部方法 // 在這裏傳遞參數 context,即覆寫 context.ownerDocument || preferredDoc 的方法 。 // 469-838 setDocument() // 在setDocument中,若是 context存在,那麼就設置覆寫 context.ownerDocument ,不然覆寫 preferredDoc if ((context ? context.ownerDocument || context : preferredDoc) !== document) { setDocument(context); } context = context || document; results = results || []; // 若是不是字符串,或者爲null或undefined,即當即返回 if (!selector || typeof selector !== "string") { return results; } //若是上下文不是dom元素,document,fragment文檔片斷之一,那麼返回空數組 if ((nodeType = context.nodeType) !== 1 && nodeType !== 9 && nodeType !== 11) { return []; } // documentIsHTML 表示爲HTML文檔, seed表示種子元素 。 // 若是給出了種子元素,那麼直接跳到過濾步驟 再也不快速查找 if (documentIsHTML && !seed) { //不能在文檔片斷下使用快速匹配,由於文檔片斷再也不document節點下, 而是在本身的節點下 ,使用fragmentDocument.getE... 查找. if (nodeType !== 11 && (match = rquickExpr.exec(selector))) { // 快速匹配正則表達式 135 行, rquickExpr = /^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/, if ((m = match[1])) { // id 選擇器 if (nodeType === 9) { // 若是上下文是document elem = context.getElementById(m); // 防止藍莓 4.6 版本,還會選擇到已經不在document樹中的元素 。 if (elem && elem.parentNode) { // 防止IE,Opera,Webkit 低版本 會返回name 也是這個選擇器的元素。 if (elem.id === m) { results.push(elem); return results; } } else { // 若是父元素不存在,說明已經不在DOM樹中了 。 return results; } } else { // 若是上下文不是document,那麼要調用 document的方法,由於普通元素沒有getElementById 方法 。由於在前面setDocument已經保證過 context.ownerDocument 必定包含原生document的方法。 if (context.ownerDocument && (elem = context.ownerDocument.getElementById(m)) && contains(context, elem) && elem.id === m) { // 716 行,contains 方法保證了 elem 必定位於context的dom樹內。 results.push(elem); return results; } } } else if (match[2]) { // Tag 選擇器 // 這裏必定要用apply,由於 context.getElementsByTagName(selector) 是一個類數組,經過 apply將其解構 。 push.apply(results, context.getElementsByTagName(selector)); return results; } else if ((m = match[3]) && support.getElementsByClassName) { // 類選擇器, apply原理同上 。 // 521 行檢測,support 是作功能檢測 。 <IE9 不支持 getElementsByClassName push.apply(results, context.getElementsByClassName(m)); return results; } } // 若是隻是 querySelector ,那麼直接調用這個接口 rbuggyQSA檢測是否存在bug // 若是 rbuggyQSA 不存在bug,就可使用 // 若是存在bug,可是選擇器中沒有包含bug 的部分。 if (support.qsa && (!rbuggyQSA || !rbuggyQSA.test(selector))) { // 隨機數 nid = old = expando; // 上下文 newContext = context; // querySelectorAll實現存在BUG,它會在包含本身的集合內查找符合本身的元素節點。 可是根據規範,應該是當前上下文的全部子孫下找, newSelector = nodeType !== 1 && selector; // 若是上下文是 Element,就要處理 querySelectorAll的 BUG 。 if (nodeType === 1 && context.nodeName.toLowerCase() !== "object") { groups = tokenize(selector); // 若是存在ID,則將ID取得出來放到這個分組的最前面,好比div b --> [id=xxx] div b // 不存在ID,就建立一個ID,重複上面的操做,但最後會刪掉此ID // 這裏的實現實際上是很是巧妙的 。 // 舉一個例子,存在選擇器 .pawn span 。而且上下文是 document.querySelector(".pawn") 。 // 因爲 querySelectorAll的 bug,查找的時候依舊會查找到span,可是根據原則,應當在.pawn 的下面查找,也就是不能容許span 。 // 做者想到一種辦法,給 .pawn 再加一個id,好比 :shit123,那麼選擇器變爲 #shit123 .pawn span。 // 可是,#shit123 和 .pawn 是同級的,#shit123 .pawn 選擇器就存在矛盾,便沒法選擇到span。 // 把選擇器重組以後,再將父元素向上提一層 。 //這樣並不會形成遺漏,由於若是 選擇器最高層爲context,按照要求沒法選中,重組以後又矛盾,天然沒法選中 ,若是最高層不是context,那麼外面包一層context,再將父元素向上提一層也無妨 。 if ((old = context.getAttribute("id"))) { //138行, 從新轉義字符 rescape = /'|\\/g, nid = old.replace(rescape, "\\$&"); } else { // 若是沒有id,則設置id 。 context.setAttribute("id", nid); } // ID 屬性選擇器 nid = "[id='" + nid + "'] "; i = groups.length; while (i--) { groups[i] = nid + toSelector(groups[i]); } // 137 行, rsibling = /[+~]/, // function testContext(context) { // return context && typeof context.getElementsByTagName !== "undefined" && context; // } // IE8下若是元素節點爲Object,沒法找到元素 。所以上下文若是是Object節點,那麼向上提一層 newContext = rsibling.test(selector) && testContext(context.parentNode) || context; newSelector = groups.join(","); } // 若是上下文是 元素,而且是Object,那麼 newSelector 爲假,直接跳過此步驟。 // IE<9 在object下獲取不到元素 。 if (newSelector) { try { push.apply(results, newContext.querySelectorAll(newSelector) ); return results; } catch (qsaError) { } finally { if (!old) { context.removeAttribute("id"); } } } } } // // 其餘複雜的狀況就調用select方法進行選擇 // whitespace = "[\\x20\\t\\r\\n\\f]", //經過replace 去掉兩邊的空格進行查找 。 // 103行, rtrim = new RegExp("^" + whitespace + "+|((?:^|[^\\\\])(?:\\\\.)*)" + whitespace + "+$", "g"), return select(selector.replace(rtrim, "$1"), context, results, seed); }