上一章講了正則表達式,這一章繼續咱們的前菜,瀏覽器兼容性處理。javascript
先介紹一個簡單的沙盒測試函數。css
/** * Support testing using an element * @param {Function} fn Passed the created div and expects a boolean result */ //特性檢測的一個技巧,造一個div,最後將其刪掉,提供一種簡單的沙盒(若是該div不加入到DOM樹上,則部分屬性如currentStyle會沒有) function assert( fn ) { var div = document.createElement("div"); try { return !!fn( div ); } catch (e) { return false; } finally { // Remove from its parent by default if ( div.parentNode ) { //爲了避免形成影響,最後若是該div還在DOM樹上,就將其移除掉 div.parentNode.removeChild( div ); } // release memory in IE // IE下對於不在DOM樹裏的DOM節點必須手動置爲null,關於IE內存泄露的文章不少,再也不贅述 div = null; } }
嗯,說明如註釋,接下來咱們先處理push函數,有些瀏覽器Nodelist是不能使用push的,咱們先檢測一下在當前瀏覽器下push能不能支持Nodelist,不能的話則把push換成本身的方法,使push可以支持Nodelisthtml
// Optimize for push.apply( _, NodeList ) //竟然還能夠對push進行優化,看看push支不支持Nodelist //由於Nodelist不是數組,因此在某些瀏覽器下沒有Push方法,須要人工造一個支持nodelist的push方法。 try { push.apply( (arr = slice.call( preferredDoc.childNodes )), preferredDoc.childNodes ); // Support: Android < 4.0 // Detect silently failing push.apply //防止安卓4.0如下版本靜默失敗(失敗了但不報錯)。 arr[ preferredDoc.childNodes.length ].nodeType; } catch ( e ) { //若是arr有值,即arr.length > 0,說明上述push方法是有效的,使用原生API,不然換成本身的方法 push = { apply: arr.length ? // Leverage slice if possible function( target, els ) { //這裏必定要用apply,不然會把整個els給Push進去而不是拆成一個個push push_native.apply( target, slice.call(els) ); } : // Support: IE<9 // Otherwise append directly //這裏不明白爲何。。 //由於在IE下對Nodelist執行slice會報錯找不到JScript對象,因此arr.length爲0 //http://www.jb51.net/article/24182.htm function( target, els ) { var j = target.length, i = 0; // Can't trust NodeList.length //爲何IE8下不能相信長度? while ( (target[j++] = els[i++]) ) {} target.length = j - 1; } }; }
說明如註釋,而後再來三發對於html元素向DOM元素轉換時,部分屬性會遇到的bug以及處理java
// Support: IE<8 // Prevent attribute/property "interpolation" // http://msdn.microsoft.com/en-us/library/ms536429%28VS.85%29.aspx //在IE<8下會自動串改部分屬性,好比下面的href會改爲當前地址+#,你們能夠試試。 //出現這種狀況後,當咱們要取某些屬性時,交給代理函數進行處理,好比Expr.attrHandle if ( !assert(function( div ) { div.innerHTML = "<a href='#'></a>"; return div.firstChild.getAttribute("href") === "#" ; }) ) { addHandle( "type|href|height|width", function( elem, name, isXML ) { if ( !isXML ) { //getAttribute我搜了一下。。沒有第二個參數,還沒翻標準。。暫且認爲是錯的。
//我靠。。還真有第二個參數。。http://msdn.microsoft.com/en-us/library/ms536429%28VS.85%29.aspx return elem.getAttribute( name, name.toLowerCase() === "type" ? 1 : 2 ); } }); } // Support: IE<9 // Use defaultValue in place of getAttribute("value") if ( !support.attributes || !assert(function( div ) { div.innerHTML = "<input/>"; div.firstChild.setAttribute( "value", "" ); return div.firstChild.getAttribute( "value" ) === ""; }) ) { addHandle( "value", function( elem, name, isXML ) { if ( !isXML && elem.nodeName.toLowerCase() === "input" ) { return elem.defaultValue; } }); } // Support: IE<9 // Use getAttributeNode to fetch booleans when getAttribute lies //使用getAttribute獲取那些非attrName=attrValue形式的attr屬性時,會出錯,因此換成使用getAttributeNode,而後判斷得到的Attr節點的specified屬性,看看是否指定。 if ( !assert(function( div ) { return div.getAttribute("disabled") == null; }) ) { addHandle( booleans, function( elem, name, isXML ) { var val; if ( !isXML ) { return elem[ name ] === true ? name.toLowerCase() : (val = elem.getAttributeNode( name )) && val.specified ? val.value : null; } }); }
說明如註釋,接下來咱們先聲明一個對象,這個對象相似於配置文件(config)的做用,嗯。。配置文件一般很長node
Expr = Sizzle.selectors = { // Can be adjusted by the user //緩存的長度 cacheLength: 50, //用來標識奇葩函數 createPseudo: markFunction, //存放用來匹配的表達式 match: matchExpr, //前面說過的,存放提取部分特殊屬性的handle attrHandle: {}, //查找過程所用的函數就存在這,後面會進行聲明 find: {}, //前面說個token中有一種類型是表示兩個元素的關係,這個關係處理函數用relative保存起來,first用來表示是不是查找到的第一個元素。 //例如假設tokens[i]是{type:'>',value:'>'},調用body[Expr.relative[tokens[i].type][dir]], relative: { ">": { dir: "parentNode", first: true }, " ": { dir: "parentNode" }, "+": { dir: "previousSibling", first: true }, "~": { dir: "previousSibling" } },
在Expr裏還有三個部分:preFilter(用來對捕獲組進行預處理),filter(返回一個個matcher,最後將多個matcher編譯成一個),pseudo(其實就是filter的一種),後續可能留出篇幅來進行講解。jquery
接下來咱們休息一下,再一口氣啃掉一個超長的初始化函數。這個初始化的主要做用是從健壯性的考慮出發,設置一下文檔節點,檢查一下是否是HTML,檢查一下各個原生API是否是好用,檢查一下querySelectorAll是否是好用,初始化查找過程用的函數(好比Expr.find['ID']);css3
/** * Sets document-related variables once based on the current document * @param {Element|Object} [doc] An element or document object to use to set the document * @returns {Object} Returns the current document */ //基於當前文檔節點設置一些文檔相關的內容,如支持性什麼的。 setDocument = Sizzle.setDocument = function( node ) { console.log('setDocument in'); var hasCompare, doc = node ? node.ownerDocument || node : preferredDoc, //得到文檔節點的一種方式,這樣寫的好處是保持健壯性 parent = doc.defaultView; // If no document and documentElement is available, return //nodeType不等於9說明不是文檔節點 //作這麼多判斷。。嗯。。仍是爲了健壯性 if ( doc === document || doc.nodeType !== 9 || !doc.documentElement ) { return document; } // Set our document document = doc; docElem = doc.documentElement; // Support tests documentIsHTML = !isXML( doc ); // Support: IE>8 // If iframe document is assigned to "document" variable and if iframe has been reloaded, // IE will throw "permission denied" error when accessing "document" variable, see jQuery #13936 // IE6-8 do not support the defaultView property so parent will be undefined //這裏是一個jQuery的bug修復 if ( parent && parent !== parent.top ) { // IE11 does not have attachEvent, so all must suffer if ( parent.addEventListener ) { parent.addEventListener( "unload", function() { setDocument(); }, false ); } else if ( parent.attachEvent ) { parent.attachEvent( "onunload", function() { setDocument(); }); } }
/* Attributes ---------------------------------------------------------------------- */ // Support: IE<8 // Verify that getAttribute really returns attributes and not properties (excepting IE8 booleans) //若是這裏檢測經過的話,後面獲取屬性值用的就是getAttribute這個API,不然用的就是getAttributeNode support.attributes = assert(function( div ) { div.className = "i"; return !div.getAttribute("className"); }); /* getElement(s)By* ---------------------------------------------------------------------- */ // Check if getElementsByTagName("*") returns only elements //檢查getElementsByTagName是否會包含註釋節點,最好不要包含。 support.getElementsByTagName = assert(function( div ) { div.appendChild( doc.createComment("") ); return !div.getElementsByTagName("*").length; }); // Check if getElementsByClassName can be trusted //?????細節 support.getElementsByClassName = rnative.test( doc.getElementsByClassName ) && assert(function( div ) { div.innerHTML = "<div class='a'></div><div class='a i'></div>"; // Support: Safari<4 // Catch class over-caching div.firstChild.className = "i"; // Support: Opera<10 // Catch gEBCN failure to find non-leading classes //Opera<10 的時候當一個元素有多個class的時候,得到第二個會出錯。 return div.getElementsByClassName("i").length === 2; }); // Support: IE<10 // Check if getElementById returns elements by name // The broken getElementById methods don't pick up programatically-set names, // so use a roundabout getElementsByName test //????因此迂迴使用getElementsByName來檢測?不明原理 support.getById = assert(function( div ) { docElem.appendChild( div ).id = expando; return !doc.getElementsByName || !doc.getElementsByName( expando ).length; }); //Expr裏的函數分爲查找和過濾兩種功能類型。 //接下來分別進行這兩種函數功能性的檢測和支持 // ID find and filter if ( support.getById ) { Expr.find["ID"] = function( id, context ) { //??????爲何要轉換成字符串形式的undefined if ( typeof context.getElementById !== strundefined && documentIsHTML ) { var m = context.getElementById( id ); // Check parentNode to catch when Blackberry 4.6 returns // nodes that are no longer in the document #6963 //!!!!在黑莓4.6的瀏覽器中會返回那些不在DOM樹中的節點,因此經過該節點是否有父節點來判斷該節點是否在DOM樹中,bug修正,而且轉換爲數組形式。 return m && m.parentNode ? [m] : []; } }; //這裏返回一個後面用來作matcher的函數 Expr.filter["ID"] = function( id ) { //對id作編碼轉換,這是一種閉包類型。 var attrId = id.replace( runescape, funescape ); return function( elem ) { return elem.getAttribute("id") === attrId; }; }; } else { // Support: IE6/7 // getElementById is not reliable as a find shortcut //???getElementById不可靠?? //!!!!刪掉之後,Sizzle就再也不經過ID來獲取節點,獲取屬性的方式也由getAttribute變爲getAttributeNode delete Expr.find["ID"]; Expr.filter["ID"] = function( id ) { var attrId = id.replace( runescape, funescape ); return function( elem ) { //每一個DOM元素下有隱含的屬性節點,經過查看其屬性節點的方式來過濾 var node = typeof elem.getAttributeNode !== strundefined && elem.getAttributeNode("id"); return node && node.value === attrId; }; }; } // Tag Expr.find["TAG"] = support.getElementsByTagName ? function( tag, context ) { console.log('find tag begin'); if ( typeof context.getElementsByTagName !== strundefined ) { return context.getElementsByTagName( tag ); } } : //不然採用過濾掉註釋節點的寫法。 function( tag, context ) { console.log('find tag begin'); var elem, tmp = [], i = 0, results = context.getElementsByTagName( tag ); // Filter out possible comments //必須過濾註釋節點 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 ) { console.log('find class begin'); if ( typeof context.getElementsByClassName !== strundefined && documentIsHTML ) { return context.getElementsByClassName( className ); } };
/* QSA/matchesSelector ---------------------------------------------------------------------- */ // QSA and matchesSelector support // matchesSelector(:active) reports false when true (IE9/Opera 11.5) rbuggyMatches = []; // qSa(:focus) reports false when true (Chrome 21) // We allow this because of a bug in IE8/9 that throws an error // whenever `document.activeElement` is accessed on an iframe // So, we allow :focus to pass through QSA all the time to avoid the IE error // See http://bugs.jquery.com/ticket/13378 //用來存放有bug的QSA字符串,最後用|鏈接起來看成正則表達式,用來檢測選擇符是否有bug rbuggyQSA = []; if ( (support.qsa = rnative.test( doc.querySelectorAll )) ) { // Build QSA regex // Regex strategy adopted from Diego Perini //這兩個assert沒有返回值,主要是把有bug的QSA字符串檢測出來。 assert(function( div ) { // Select is set to empty string on purpose // This is to test IE's treatment of not explicitly // setting a boolean content attribute, // since its presence should be enough // http://bugs.jquery.com/ticket/12359 //這個bug檢測很簡練 div.innerHTML = "<select> <option selected="selected"></option> </select>"; // Support: IE8, Opera 10-12 // Nothing should be selected when empty strings follow ^= or $= or *= //!!!空白字符串不該該跟在 ^=、$=、*=這樣的字符後面,不然邏輯上是走不通的,你想一想啊,^=''的意思是匹配開頭爲空字符的字符串。。哪一個字符串開頭是空字符?,剩下同理 if ( div.querySelectorAll("[t^='']").length ) { rbuggyQSA.push( "[*^$]=" + whitespace + "*(?:''|\"\")" ); } // Support: IE8 // Boolean attributes and "value" are not treated correctly //!!!IE8中的QSA不能正確識別非key=value形式的屬性選擇符 if ( !div.querySelectorAll("[selected]").length ) { rbuggyQSA.push( "\\[" + whitespace + "*(?:value|" + booleans + ")" ); } // Webkit/Opera - :checked should return selected option elements // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked // IE8 throws error here and will not see later tests //!!!!:checked僞類選擇器理論應該返回被選擇的option元素,但IE8下有bug。。 if ( !div.querySelectorAll(":checked").length ) { rbuggyQSA.push(":checked"); } }); assert(function( div ) { // Support: Windows 8 Native Apps // The type and name attributes are restricted during .innerHTML assignment //????臥槽。。win8不能直接用innerHTML來建立type和name屬性? var input = doc.createElement("input"); input.setAttribute( "type", "hidden" ); div.appendChild( input ).setAttribute( "name", "D" ); // Support: IE8 // Enforce case-sensitivity of name attribute //!!!!加強對name大小寫的敏感性,若是大小寫不敏感,則不能使用帶有name=.的屬性選擇符 if ( div.querySelectorAll("[name=d]").length ) { rbuggyQSA.push( "name" + whitespace + "*[*^$|!~]?=" ); } // FF 3.5 - :enabled/:disabled and hidden elements (hidden elements are still enabled) // IE8 throws error here and will not see later tests //!!!!理論上hidden的元素還應該能用:enabled僞類選擇符選擇到的,但IE8和FF3.5下有bug if ( !div.querySelectorAll(":enabled").length ) { rbuggyQSA.push( ":enabled", ":disabled" ); } // Opera 10-11 does not throw on post-comma invalid pseudos //????? div.querySelectorAll("*,:x"); rbuggyQSA.push(",.*:"); }); }
if ( (support.matchesSelector = rnative.test( (matches = docElem.webkitMatchesSelector || docElem.mozMatchesSelector || docElem.oMatchesSelector || docElem.msMatchesSelector) )) ) { assert(function( div ) { // Check to see if it's possible to do matchesSelector // on a disconnected node (IE 9) //!!!!檢測是否matchesSelector會匹配到沒有鏈接的節點 support.disconnectedMatch = matches.call( div, "div" ); // This should fail with an exception // Gecko does not error, returns false instead //????? matches.call( div, "[s!='']:x" ); rbuggyMatches.push( "!=", pseudos ); }); } rbuggyQSA = rbuggyQSA.length && new RegExp( rbuggyQSA.join("|") ); rbuggyMatches = rbuggyMatches.length && new RegExp( rbuggyMatches.join("|") ); /* Contains ---------------------------------------------------------------------- */ hasCompare = rnative.test( docElem.compareDocumentPosition ); // Element contains another // Purposefully does not implement inclusive descendent // As in, an element does not contain itself //這裏和懶函數的方式殊途同歸,若是有原生compareDocumentPosition或contains contains = hasCompare || rnative.test( docElem.contains ) ? function( a, b ) { console.log('contains begin'); var adown = a.nodeType === 9 ? a.documentElement : a, bup = b && b.parentNode; return a === bup || !!( bup && bup.nodeType === 1 && ( adown.contains ? adown.contains( bup ) : //這裏&上16僅僅是判斷a是否包含bup,b若是是a自己,則二者應該不算包含關係 //http://www.2cto.com/kf/201301/181075.html a.compareDocumentPosition && a.compareDocumentPosition( bup ) & 16 )); } : //不然就用土辦法不斷遍歷DOM樹 function( a, b ) { if ( b ) { while ( (b = b.parentNode) ) { if ( b === a ) { return true; } } } return false; }; /* Sorting ---------------------------------------------------------------------- */ // Document order sorting //用來傳給sort函數的排序方法 sortOrder = hasCompare ? function( a, b ) { // Flag for duplicate removal if ( a === b ) { hasDuplicate = true; return 0; } // Sort on method existence if only one input has compareDocumentPosition //!undefined === true //!function (){} === false //if(-1){console.log(1);} -->1 //用以檢查是否兩個輸入都有該API var compare = !a.compareDocumentPosition - !b.compareDocumentPosition; if ( compare ) { return compare; } // Calculate position if both inputs belong to the same document compare = ( a.ownerDocument || a ) === ( b.ownerDocument || b ) ? a.compareDocumentPosition( b ) : // Otherwise we know they are disconnected 1; // Disconnected nodes if ( compare & 1 || (!support.sortDetached && b.compareDocumentPosition( a ) === compare) ) { // Choose the first element that is related to our preferred document if ( a === doc || a.ownerDocument === preferredDoc && contains(preferredDoc, a) ) { return -1; } if ( b === doc || b.ownerDocument === preferredDoc && contains(preferredDoc, b) ) { return 1; } // Maintain original order return sortInput ? //最後這裏處理了給字符串調用sort的狀況 //後面有一句代碼 //support.sortStable = expando.split("").sort( sortOrder ).join("") === expando; ( indexOf.call( sortInput, a ) - indexOf.call( sortInput, b ) ) : 0; } return compare & 4 ? -1 : 1; } : //若是沒有contains這樣的原生API使用 function( a, b ) { // Exit early if the nodes are identical if ( a === b ) { hasDuplicate = true; return 0; } var cur, i = 0, aup = a.parentNode, bup = b.parentNode, ap = [ a ], bp = [ b ]; // Parentless nodes are either documents or disconnected //只要其中有一個元素沒有父元素 if ( !aup || !bup ) { return a === doc ? -1 : b === doc ? 1 : aup ? -1 : bup ? 1 : sortInput ? ( indexOf.call( sortInput, a ) - indexOf.call( sortInput, b ) ) : 0; // If the nodes are siblings, we can do a quick check //或者兩個元素的父元素是兄弟,就能夠立馬斷定 } else if ( aup === bup ) { return siblingCheck( a, b ); } // Otherwise we need full lists of their ancestors for comparison //不然遍歷出全部祖先路徑,而後一一對比,直到找到分歧點,再比較分歧點的序數便可 cur = a; while ( (cur = cur.parentNode) ) { ap.unshift( cur ); } cur = b; while ( (cur = cur.parentNode) ) { bp.unshift( cur ); } // Walk down the tree looking for a discrepancy while ( ap[i] === bp[i] ) { i++; } return i ? // Do a sibling check if the nodes have a common ancestor siblingCheck( ap[i], bp[i] ) : // Otherwise nodes in our document sort first ap[i] === preferredDoc ? -1 : bp[i] === preferredDoc ? 1 : 0; }; //最後返回文檔節點 return doc; };
上面這個初始化函數實在太長。。再調用一下就好。web
// Initialize against the default document setDocument();