上一篇文章寫的關於
Sizzle
的正則表達式,我打算就按照Sizzle
的運行順序進行閱讀文檔,因此身先士卒就是這個setDocument
函數了,這個函數主要作了瀏覽器的原生選擇器支持狀況判斷、老瀏覽器的兼容、querySelectorAll
在個瀏覽器的bug排查和排序函數的初始化。函數會在Sizzle
被引用的時候當即執行一次,而且在每次使用Sizzle
的時候都會調用,讀懂這個函數會涉及Sizzle
一些其餘的變量,這裏我會酌情加到這篇文章裏面,方便你們閱讀。javascript
這裏的全局指的是IIFE中的做用域 html
preferredDoc = window.document
document
這裏的document
是個變量不是咱們所熟知的那個documentdocumentIsHTML
判斷當前文檔是否是HTML 由於Sizzle
是支持XML
的support
這個是判斷兼容用的是一個對象Expr
這個之後會單說,這個也是一個對象,這個對象是作搜索以及過濾元素用的hasDuplicate
判斷元素是否有重複sortInput
這個就是沒有進行排序的最終元素集rnative
判斷是否是原生方法用的正則expando = 'sizzle' + 1 * new Date()
做爲惟一標識使用runescape
上一篇文章有, 正則, 判斷轉義字符//判斷是否爲XML isXML = function(elem) { var namespace = elem.namespaceURI, docElem = (elem.ownerDocument || elem).documentElement; return !rhtml.test(namespace || docElem && docElem.nodeName || 'HTML'); } 複製代碼
//用於驗證,這個方法在Sizzle中使用很是頻繁, 幾乎全部的判斷兼用都用了這個方法 function assert(fn) { var el = document.createElement("fieldset"); try { return !!fn(el); } catch(e) { return false; } finally { if (el.parentNode) { el.parentNode.removeChild(el); } el = null; } } 複製代碼
//轉義用的 funescape = function(escape, nonHex) { var high = '0x' + escape.slice( 1 ) - 0x10000; return noHex ? noHex: high < 0 ? String.fromCharCode(high + 0x10000) : String.fromCharCode(high >> 10 | 0xD800, high & 0x3FF | 0xDC00) } 複製代碼
function siblingCheck(a, b) { var cur = b && a, diff = cur && a.nodeType === 1 && b.nodeType == 1 && a.sourceIndex - b.sourceIndex; if (diff) { return diff } if (cur) { while((cur = cur.nextSibling)) { if (cur === b) { return -1 } } } return a ? 1 : -1; } 複製代碼
setDocument方法會在IIFE
中執行一次,而且沒有參數,用來初始化。
方法分爲幾個部分。1.原生搜索方法兼容,2.根據兼容設置Expr,3.querySelectorAll兼容問題,4.設置排序。java
/* @param {Element|Object} 一個元素或者文檔元素用來設置document變量 */ setDocument = Sizzle.setDocument = function(node) { var hasCompare, subWindow, doc = node ? node.ownerDocument || node : preferredDoc; // 這裏若是不是document元素或者沒有html,再或者和上一次調用的時候元素處在同一個document下面 //那就直接返回, 性能優化 if (doc == document || doc.nodeType !== 9 || !doc.documentElement) { return document; } //更新外部的全局變量 document = doc; docElem = document.documentElement; documentIsHTML = !isXML(document); //這塊就是兼容iframe的 if ( perferredDoc != document && (subWindow = document.defaultView) && subWindow.top !== subWindow ) { if ( subWindow.addEventListener ) { subWindow.addEventListener('unload', unloadHandler, false); } else if ( subWindow.attachEvent ) { subWindow.attachEvent('onunload', unloadHandler); } } } 複製代碼
這部分就是作了更新全局變量、性能優化、以及iframe的兼容。node
// IE/Edge和老瀏覽器不支持:scope這個僞類 // 判斷會不會有選中的元素(由於沒有添加:scope這個僞類, 因此length應該爲0) // 若是length不等於0,那麼不支持; 若是爲0,就是支持 support.scope = assert( function( el ) { docElem.appendChild( el ).appendChild( document.createElement('div') ); return typeof el.querySelectorAll !== 'undefined' && !el.querySelectorAll(':scope fieldset div').length } ); //判斷屬性與特性是否重疊 //老IE在給元素的特性賦值後, 能夠經過獲取相同的屬性名獲取到該值 support.attributes = assert( function( el ) { el.className = 'i'; return !el.getAttribute('className'); } ); //檢查getElementsByTagName是否只返回元素節點 support.getElementsByTagName = assert( function( el ) { el.appendChild(document.createComment('')); return !el.getElementsByTagName('*').length } ); //判斷getElementsByClassName是否爲瀏覽其原生方法 support.getElementsByClassName = rnative.test(document.getElementsByClassName); // 檢查若是getElementById是不是經過name返回元素的 // 可是老版本的getElementById方法不能拿到編程方式設置的id // 因此使用getElementsByName看看經過這個方法時候能獲取id support.getById = assert( function( el ) { docElem.appendChild(el).id = expando; return !document.getElementsByName || !document.getElementsByName(expando).length; } ) 複製代碼
這裏就是根據剛剛的support狀況去設置Expr。關於Expr我估計我會單獨用一篇文章去寫它,雖然沒看過它,可是感受這兄弟應該很重要,不急慢慢寫。web
//ID if (support.getById) { Expr.filter["ID"] = function(id) { //轉碼 var attrId = id.replace(runescape, funescape); return function(elem) { return elem.getAttribute('id') == attrId; } }; Expr.find["ID"] = function(id, context) { if (typeof context.getElementById !== 'undefined' && documentIsHTML) { var elem = context.getElementById(id); return elem ? [elem] : [] } } } else { Expr.filter["ID"] = function(id) { //轉碼 var attrId = id.repalce(runescape, funescape); return function(elem) { var node = typeof elem.getAttributeNode !== 'undefined' && elem.getAttributeNode('id'); return node && node.value === attrId; } }; //IE 6 -7 的時候getElementById不是一個可靠的查找方法 Expr.find["ID"] = function(id, context) { if (typeof context.getElemntById !== 'undefined' && documentIsHTML) { var node, i, elems, elem = context.getElementById(id); if (elem) { node = elem.getAttributeNode('id'); if (node && node.value === id) { return [elem]; } elems = context.getElementsByName(id); i = 0; while(elem = elems[i++]) { node = elem.getAttributeNode('id'); if (node && node.value === id) { return [elem] } } } return []; } } } 複製代碼
getElementById
在老IE版本中也就是IE六、7中,會返回第一個name或者id爲匹配值的元素,因此在獲取到元素後,經過獲取元素的id
屬性並判斷是否等於輸入的值,才能正確判斷元素。可是還有一個問題是就是老的IE的getAttribute
的問題,具體能夠看司徒大大寫的這篇博客。正則表達式
//Tag Expr.find['TAG'] = support.getElementsByTagName ? function(tag, context) { if (typeof context.getElementsByTagName !== 'undefined') { return context.getElementsByTagName(tag); //DocumentFragment節點是沒有getElementsByTagName方法的 } else if (support.qsa) { return context.querySelectorAll(tag); } } : function(tag, context) { var elem, tmp = [], i = 0, results = context.getElementsByTagName(tag); //把不是元素的Node節點過濾掉 if (tag === '*') { while ( ( elem = results[i++] ) ) { if (elem.nodeType === 1) { tmp.push(elem); } } return tmp; } return results; } 複製代碼
getElementsByTag
這個就比較簡單了,若是是全選話,把不是元素的節點過濾掉就行了。編程
//Class Expr.find['CLASS'] = support.getElementsByClassName && function(className, context) { if (typeof context.getElementsByClassName !== 'undefined' && documentIsHTML) { return context.getElementsByClassName(className); } } 複製代碼
getElementsByClassName
就是有就用,沒有就不用了。瀏覽器
這裏先判斷了瀏覽器支不支持querySelectorAll
和matches
這兩個方法。若是支持的話,再去判斷兼容問題。這部分看的時候比較難懂,由於不少兼容都沒有碰到過,幾乎都是按照註釋來看的,這些兼容能夠不用太留心,關鍵是理解一下思路,好比故意找報錯讓外部的函數catch
而再也不執行下面的代碼。我第一次看的時候以爲很是的酷。ruby
rbuggyMatches = []; rbuggyQSA = []; if ((suuport.qsa = rnatvie.test(document.querySelelctorAll))) { assert( function (el) { var input; docElem.appendChild(el).innerHTML = "<a id='" + expando + "'></a>" + "<select id='" + expando + "-\r\\' msallowcapture=''>" + "<option selected=''></option></select>"; //^= $= *=後面接空字符串時, 應該什麼都不選中 if (el.querySelectorAll("[msallowcapture^='']").length) { rbuggyQSA.push("[*^$]=" + whitespace + "*(?:''|\"\")"); } //IE8 仍是布爾屬性和'value'屬性不能正常處理 if (!el.querySelectorAll("[selected]").length) { rbuggyQSA.push("\\[" + whitespace + "*(?:value|" + booleans + ")"); } //\r是空格因此應該能夠匹配的到 if (!el.querySelectorAll("[id~=" + expando + "-]").length) { rbuggyQSA.push('~='); } input = document.createElement("input"); input.setAttribute("name", ""); el.appendChild(input); //IE11和Edge在某些狀況下找不到[name=""]的元素 //可是有意思的是 老的IE卻沒有問題 if (!el.querySelectorAll("[name='']").length) { rbuggQSA.push("\\[" + whitespace + "*name" + whitespace + "*=" + whitespace + "*(?:''|\"\")"); } //IE8在這裏會報錯, 而後被assert方法catch下面的代碼不會再執行 //這裏是能夠選擇select中被selected的option的 if (!el.querySelectorAll(":checked").length) { rbuggyQSA.push(":checked"); } //Safari 8+, IOS 8+ //ID + 兄弟選擇器會失效 if (!el.querySelectorAll("a#" + expando + "+*").length) { rbuggyQSA.push(".#.+[+~]"); } //當用的是錯誤的轉義字符的話, 只有老火狐不會報錯 //若是報錯, 仍是會直接走assert的catch了, 就不會運行最後這個push了, 這個寫法很是的酷; el.querySelectorAll('\\\f'); rbuggyQSA.push("[\\r\\n\\f]"); } ); assert(function (el) { el.innerHTML = "<a href='' disabled='disabled'></a>" + "<select disabled='disabled'><option/></select>"; var input = document.createElement("input"); input.setAttribute("type", "hidden"); el.appendChild( input ).setAttribute("name", "D"); // name屬性區分大小寫的問題 if (el.querySelectorAll("[name=d]").length) { rbuggyQSA.push("name" + whitespace + "*[*^$|!~]?="); } // option 和 input 應該被正常選中 // IE8在這裏會報錯 下面的test都再也不執行 if (el.querySelectorALL(':enabled').length !== 2) { rbuggyQSA.push(":enabled", ":disabled"); } // IE9-11 :disabled不選擇disabled的fieldset元素的子集 docElem.appendChild(el).disabled = true; if (el.querySelectorAll(':disabled').length !== 2) { rbuggyQSA.push(':enabled', ':disabled'); } // Opera 10-11 逗號後僞類無效是不會報錯的; // 這裏仍是 若是報錯了 就直接被catch不添加這個規則 // 若是沒報錯就加進去 el.querSelectorAll('*,:x'); rbuggyQSA.push(',.*:'); } ); } if ( (support.matchesSelector = rnative.test( (matches = docElem.matches || docElem.webkitMatchesSelector || docElem.mozMatchesSelector || docElem.oMatchesSelector || docElem.msMactesSelector) ) ) ) { assert(function(el) { // 不在DOM樹上的元素, 是否能夠用matches檢測 support.disconnectedMatch = matches.call(el, "*"); // 這裏本應該報錯的, 可是Gecko只返回false // 這裏也是,若是報錯直接catch matches.call(el, '[s!= ""]:x'); rbuggyMatches.push('!=', pseudos ); } ); } // 轉成正則表達式 或關係 rbuggyQSA = rbuggyQSA.length && new RegExp( rbuggyQSA.join( "|" ) ); rbuggyMatches = rbuggyMatches.length && new RegExp( rbuggyMatches.join( "|" ) ); 複製代碼
Sizzle的排序思路是使用了原生的Array.prototype.sort
方法,因此所謂排序其實就是配置sort
的回調函數,咱們設置好規則,讓瀏覽器來幫咱們進行排序。主要排序用的就是compareDocumentPosition
這個方法,Sizzle
會先判斷有沒有這個方法, 若是沒有那就只能遍歷了。性能優化
hasCompare = rnative.test(docElem.compareDocumentPosition); contains = hasCompare || rnative.test(docELem.contains) ? function (a, b) { var adown = a.nodeType === 9 ? a.documentElement : a, bup = b && b.parentNode; return a === bup || !!(bup && bup.nodeType === 1 && ( adown.contains ? adown.contains( bup ) : a.compareDocumentPosition && a.compareDocumentPosition(bup) & 16 ) ) } : function (a, b) { if (b) { while ((b = b.parentNode)) { if (a === b) { return true; } } } return false; } sortOrder = hasCompare ? function(a, b) { if (a === b) { hasDuplicate = true; return 0; } //若是隻有一個有compareDocumentPosition //誰有誰排前面 var compare = !a.compareDocumentPosition - !b.compareDocumentPostion; if (compare) { return compare; } //查看一下是否是一個在一個document下 //若是不是的話 那麼兩個節點之間不存在關係 compare = (a.ownerDocument || a) == (b.ownerDocument || b) ? a.compareDocumentPosition(b) : 1; /** suport.sortDetached = assert(function( el ) { 這裏應該返回1 可是有可能返回4 return el.compareDocumentPostion(document.createElement('fieldset')) & 1; }) **/ // 若是這裏是有不在DOM樹中的元素 // 誰在DOM中誰在前面 // 若是兩個都不在 那就按本來的位置排列 if (compare & 1 || (!suport.sortDetached && compare === b.compareDocumentPosition(a)) ) { if (a == document || a.ownerDocument == preferredDoc && contains(preferredDoc, a)) { return -1; } if (b == document || b.ownerDocument == preferredDoc && contains(preferredDoc, b)) { return 1; } return sortInput ? (indexOf(sortInput, a) - indexOf(sortInput, b)) : 0; } return compare & 4 ? -1 : 1; } : function (a, b) { if (a === b) { hasDuplicate = true; return 0; } var cur, i = 0, aup = a.parentNode, bup = b.parentNode, ap = [a], bp = [b]; // 若是沒有父節點 那麼不是document就是不在DOM樹上 if (!aup || !bup) { return a == document ? -1 : //若是a是document, a在前面 b == document ? 1 : //若是b是document, b在前面 aup ? -1 : //若是a在DOM樹上, a在前面 bup ? 1 : //若是b在DOM樹上, b在前面 sortInput ? //若是都不在 那就正常排序把 (indexOf(sortInput, a) - indexOf(sortInput, b)) : 0; } else if (aup === bup) { // 若是父級相同 那麼能夠經過nextSibling直接判斷 return siblingCheck(a, b); } // 若是都不是的話 那就從頭開始比 cur = a; while( (cur = cur.parentNode) ) { ap.unshift(cur); } cur = b; while( (cur = cur.parentNode) ) { bp.unshift(cur); } while(ap[i] === bp[i]) { i++; }; return i ? //找到了不一樣的父集節點 仍是繼續經過nextSibling判斷誰在前面 siblingCheck(ap[i], bp[i]) : ap[i] === perferredDoc ? -1 : bp[i] === preferredDoc ? 1 : 0 } 複製代碼
setDocument = Sizzle.setDocument = function(node) { var hasCompare, subWindow, doc = node ? node.ownerDocument || node : preferredDoc; if (doc == document || doc.nodeType !== 9 || !doc.documentElement) { return document; } document = doc; docElem = document.documentElement; documentIsHTML = !isXML(document); if (perferredDoc != document && (subWIndow = document.defaultView) && subWindow.top !== subWindow) { if (subWIndow.addEventListener) { subWindow.addEventListener('unload', unloadnHandler, false); } else if (subWindow.attachEvent) { subWindow.attachEvent('onunload', unloadHandler); } } support.scope = assert( function(el) { docElme.appednChild(el).appendChild(document.createElement('div')); return typeof el.querySelectorAll !== 'undefined' && !el.querySelector(':scope fieldset div').length; } ); support.attributes = assert(function(el) { el.className = 'i'; return !el.getAttribute('className'); }); support.getElementsByTagName = assert( function(el) { el.appendChild(document.createComment""); return !el.getElementsByTagName('*').length; } ); support.getElementsByClassName = rnative.test(document.getElementsByClassName); support.getById = assert( function(el) { docElem.appendChild(el).id = expando; return !document.getElementsByName || !document.getElementsByName(expando).length; } ); //ID if (support.getById) { Expr.filter["ID"] = function(id) { var attrId = id.replace(runescape, funescape); return function(elem) { return elem.getAttribute('id') === attrId; } } Expr.find["ID"] = function(id, context) { if (typeof context.getElementById !== 'undeifined' && documentIsHTML) { var elem = context.getElementById( id ); return elem ? [elem] : []; } } } else { Expr.filter["ID"] = function(id) { var attrId = id.replace(runescape, funescape); return function(elem) { var node = typeof elem.getAttributeNode !== 'undefined' && elem.getAttributrNode('id'); return node && node.value === attrId; } }; Expr.find["ID"] = function(id, context) { if (typeof context.getElementById !== 'undefined' && documentIsHTML) { var node, i, elems, elem = context.getElemetById(id); if (elem) { node = elem.getAttributeNode("id"); if (node && node.value === attrId) { return [elem]; } elems = document.getElementsByName(id); i = 0; while( (elem = elems[i++]) ) { node = elem.getAttributeNode("id"); if (node && node.value === attrId) { return elem; } } } return []; } } } //Tag Expr.find["TAG"] = support.getElementsByTagName ? function(tag, context) { if (typeof context.getElementsByTagName !== 'undefined') { return context.getElementsByTagName(tag); } else if (support.qsa) { return context.querySelectorAll(tag); } } : function(tag, context) { var elem, tmp = [] i = 0, results = context.getElementsByTagName(tag); if (tag === "*") { while ( (elem = results[i++]) ) { if (elem.nodeType === 1) { tmp.push(elem); } } return tmp; } return results; } //Class Expr.find["CLASS"] = support.getElementsByClassName && function(className, context) { if (typeof context.getElementsByClassName !== 'undefined' && documentIsHTML) { return context.getElementsByClassName(className); } } rbuggyMatches = []; rbuggyQSA = []; if ((suuport.qsa = rnatvie.test(document.querySelelctorAll))) { assert( function (el) { var input; docElem.appendChild(el).innerHTML = "<a id='" + expando + "'></a>" + "<select id='" + expando + "-\r\\' msallowcapture=''>" + "<option selected=''></option></select>"; if (el.querySelectorAll("[msallowcapture^='']").length) { rbuggyQSA.push("[*^$]=" + whitespace + "*(?:''|\"\")"); } if (!el.querySelectorAll("[selected]").length) { rbuggyQSA.push("\\[" + whitespace + "*(?:value|" + booleans + ")"); } if (!el.querySelectorAll("[id~=" + expando + "-]").length) { rbuggyQSA.push('~='); } input = document.createElement("input"); input.setAttribute("name", ""); el.appendChild(input); if (!el.querySelectorAll("[name='']").length) { rbuggQSA.push("\\[" + whitespace + "*name" + whitespace + "*=" + whitespace + "*(?:''|\"\")"); } if (!el.querySelectorAll(":checked").length) { rbuggyQSA.push(":checked"); } if (!el.querySelectorAll("a#" + expando + "+*").length) { rbuggyQSA.push(".#.+[+~]"); } el.querySelectorAll('\\\f'); rbuggyQSA.push("[\\r\\n\\f]"); } ); assert(function (el) { el.innerHTML = "<a href='' disabled='disabled'></a>" + "<select disabled='disabled'><option/></select>"; var input = document.createElement("input"); input.setAttribute("type", "hidden"); el.appendChild( input ).setAttribute("name", "D"); if (el.querySelectorAll("[name=d]").length) { rbuggyQSA.push("name" + whitespace + "*[*^$|!~]?="); } if (el.querySelectorALL(':enabled').length !== 2) { rbuggyQSA.push(":enabled", ":disabled"); } docElem.appendChild(el).disabled = true; if (el.querySelectorAll(':disabled').length !== 2) { rbuggyQSA.push(':enabled', ':disabled'); } el.querSelectorAll('*,:x'); rbuggyQSA.push(',.*:'); } ); } if ( (support.matchesSelector = rnative.test( (matches = docElem.matches || docElem.webkitMatchesSelector || docElem.mozMatchesSelector || docElem.oMatchesSelector || docElem.msMactesSelector) ) ) ) { assert(function(el) { support.disconnectedMatch = matches.call(el, "*"); matches.call(el, '[s!= ""]:x'); rbuggyMatches.push('!=', pseudos ); } ); } rbuggyQSA = rbuggyQSA.length && new RegExp( rbuggyQSA.join( "|" ) ); rbuggyMatches = rbuggyMatches.length && new RegExp( rbuggyMatches.join( "|" ) ); //Contains hasCompare = rnative.test(docElem.compareDocumentPostion); contains = hasCompare || rnative.test(docElem.contains) ? function(a, b) { var adown = a.nodeType === 9 ? a.documentElement : a, bup = b && b.parentNode; return a === bup || !!( bup && bup.nodeType === 1 && ( adown.contains ? adown.contains(bup) : a.compareDocumentPosition && a.compareDocumentPosition(bup) & 16; ) ); } : function (a, b) { if (b) { while ( (b = b.parentNode) ) { if (a === b) { return true; } } } return false; } //Sorting sortOrder = hasCompare ? function(a, b) { if (a === b) { hasDuplicate = true; return 0; } var compare = !a.compareDocumentPosition - !b.compareDocumentPosition; if (compare) { return compare; } compare = (a.ownerDocument || a) == (b.ownerDocument || b) ? a.compareDocumentPosition(b) : 1 if (compare & 1 || ( !support.sortDetached && b.compareDocumentPosition(a) === compare )) { if (a == document || a.ownerDocument == preferredDoc && contains(preferredDoc, a)) { return -1; } if (b == document || b.ownerDocument == preferredDoc && contains(preferredDoc, b)) { return 1; } return sortInput ? (indexOf(sortInput, a) - indexOf(sortInput,b)) : 0; } return compare & 4 ? -1 : 1; } : function(a, b) { if (a === b) { hasDuplicate = true; return 0; } var cur, i = 0, aup = b.parentNode, bup = b.parentNode, ap = [ a ], bp = [ b ]; if (!aup || !bup) { return a == document ? -1 : b == document ? 1 : aup ? -1 : bup ? 1 : sortInput ? (indexOf(sortInput, a) - indexOf(sortInput, b)) : 0; } else if (aup === bup) { return siblingCheck(a, b); } cur = a; while( (cur = cur.parentNode) ) { ap.unshift(cur); } cur = b; while( (cur = cur.parentNode) ) { bp.unshif(cur); } while (ap[i] === bp[i]) { i++ } return i ? siblingCheck(ap[i] : bp[i]) : ap[i] = preferredDoc ? -1 : bp[i] = preferredDoc ? 1 : 0; } return document; } 複製代碼