說一下Sizzle中零碎的API。這些API有的被jQuery接管,直接使用jQuery.xxx就可使用,有的沒有被接管,若是要在jQuery中使用,使用方法是jQuery.find.xxx。html
具體看一下有哪些APInode
//篩選出elements知足CSS選擇器表達式expr的節點【最終返回的是節點數組】
Sizzle.matches = function( expr, elements ) {...}
//判斷dom元素elem是否匹配CSS選擇器表達式expr
Sizzle.matchesSelector = function( elem, expr ) {...}
//被jQuery接管的部分API
jQuery.find = Sizzle;//查找函數 jQuery.unique = Sizzle.uniqueSort;//根據DOM元素在文檔中出現的前後順序對DOM元素數組進行排序,並移除重複的元素 jQuery.text = Sizzle.getText;//獲取節點elem下的全部文本內容【包括標籤之間的空白】 jQuery.isXMLDoc = Sizzle.isXML;//判斷DOM節點是否位於XML文檔中,或者其自己就是XML文檔 jQuery.contains = Sizzle.contains;//用於判斷指定元素內是否包含另外一個元素
接下來一一分析(jQuery.find就不分析了)。web
Sizzle.contains(forefather,posterity):用於判斷指定元素內是否包含另外一個元素chrome
通俗的來說,便是判斷某一個DOM節點posterity是不是forefather的後代節點。數組
源碼比較簡單,處理過程是若是能經過瀏覽器自帶的docElem.contains或docElem.compareDocumentPosition來處理則使用之;不然一直查找posterity節點的parentNode和forefather節點比較,若是找到和forefather相同的節點則返回true,不然返回false。瀏覽器
因此,這裏關鍵的地方是瀏覽器的原生函數docElem.contains或docElem.compareDocumentPosition。dom
docElem.contains(docNode):判斷節點docNode是否包含在docElem中。【須要注意:當docNode和docElem是同一個節點的時候也返回true。這是和Sizzle.contains不一樣的地方】。這個方法並無標準化,期初用於IE,可是現代的瀏覽器都實現了這個功能。編輯器
docElem.compareDocumentPosition(docNode):返回一個比特碼用於肯定docElem和docNode兩個節點之間的位置關係。這個函數是DOM3標準的一部分。現代瀏覽器(IE9+,firefox)都支持該方法。函數
比較結果對應的描述以下post
Bits | Number | Meaning |
000000 | 0 | 元素相同 |
000001 | 1 | 節點在不一樣的文檔 |
000010 | 2 | docNode在docElem以前 |
000100 | 4 | docNode在docElem以後 |
001000 | 8 | docNode包含docElem |
010000 | 16 | docNode被docElem包含 |
100000 | 32 | 佔位(瀏覽器私有使用) |
docElem.compareDocumentPosition(docNode)的獲得的結果應當是符合的比特位相加
好比:
<div id="demo"> <div category="children"><span></span></div> <div category="cooking"></div> <div category="web" cover="paperback"></div> <div category="web"></div> </div>
js:
var xmlDoc = document.getElementById('demo');
var a=xmlDoc.getElementsByTagName('p')[0]; var b=xmlDoc.getElementsByTagName('span')[0]; document.write("<br> Number of compareDocumentPosition: " +a.compareDocumentPosition(b));
首先b在a後面,因此符合這個條件的比特值是4;其次b被a包含,符合這個條件的比特值爲16。a.compareDocumentPosition(b)的最終結果是4 + 16 = 20,比特值爲010100;考慮各類瀏覽器的兼容狀況,因此a.compareDocumentPosition( bup ) & 16的結果就是a是否包含bup的結果。
說到比較節點位置關係須要知道一些東東。XML 常常在節點之間含有換行或空白字符。這是在使用簡單的編輯器(好比記事本)時常常出現的狀況。好比下面的例子
現代瀏覽器(Firefox,chrome,IE9+)會把空的空白或換行做爲文本節點來處理。
var xmlDoc = document.getElementById('demo');
var x=xmlDoc.childNodes; document.write("Number of child nodes: " + x.length);//Number of child nodes: 9
可是IE8-瀏覽器不會這樣,瀏覽器會忽略元素節點之間的空文本節點。這個時候獲取到的節點數量不是爲9,而是4。
比較好的情況是docElem.compareDocumentPosition只有現代瀏覽器才支持,因此在支持docElem.compareDocumentPosition的狀況,各個瀏覽器比較結果都應該是相同的。
Sizzle.uniqueSort(domArray):根據DOM元素在文檔中出現的前後順序對DOM元素數組進行排序,並移除重複的元素
參數domArray只能是DOM元素數組。而且重複的元素指的是同一個節點(使用「===」比較)。
去重不是難點。在去重以前須要對節點進行排序。使用數組的sort方法。
這裏講一講sort的使用:
語法:arrayObject.sort(sortby)
參數:sortby必須是函數,用來規定排序順序,可選。
返回值:對數組(原數組,不生成副本)的引用。
說明:若是沒有使用參數,將按字母順序對數組中的元素進行排序。內部實現——應把數組的元素都轉換成字符串(若有必要)來進行比較。若是提供比較函數,該函數要比較兩個值,而後返回一個用於說明這兩個值的相對順序的數字。
比較函數應該具備兩個參數 a 和 b,其返回值以下:
- 若 a 小於 b,在排序後的數組中 a 應該出如今 b 以前,則返回一個小於 0 的值,即a-b應當爲負值。
- 若 a 等於 b,則返回 0。
- 若 a 大於 b,在排序後的數組中 a 應該出如今 b 以後,則返回一個大於 0 的值,即a - b爲正值。
明白了排序函數之後,咱們明白,由於參數是DOM節點數組,不可能使用默認的排序方式,咱們必須自定義比較函數sorby。
前面咱們分析了瀏覽器原生方法docElem.compareDocumentPosition,這個函數就是用來判斷節點關係的最好方法了。若是能使用這個函數,咱們的比較函數能夠是
function( a, b ) { var compare; if ( a === b ) {return 0; } if ( (compare = b.compareDocumentPosition && a.compareDocumentPosition && a.compareDocumentPosition( b )) ) {
//若是b在a後面,那麼compare的比特位至少是0?0100,?表示多是0,也多是1return compare & 4 ? -1 : 1; } //最後的容錯處理,若是節點a不包含compareDocumentPosition方法,咱們認爲是非法節點,直接放在數組最後。 return a.compareDocumentPosition ? -1 : 1; }
可是有一箇中特殊狀況:a和b再也不同一個文檔內。那麼就看a、b節點哪一個不在當前文檔內那麼哪一個節點就應該放在最後。這部分的判斷以下
if ( compare & 1 || a.parentNode && a.parentNode.nodeType === 11 ) {
//a就是當前文檔或是在window.document中,則需將b放在數組最後 if ( a === doc || contains( preferredDoc, a ) ) { return -1; }
//b就是當前文檔或是在window.document中,則須要將a放在數組最後 if ( b === doc || contains( preferredDoc, b ) ) { return 1; } return 0; }
若是不能使用docElem.compareDocumentPosition,判斷就稍微複雜一些。可是也能夠作一些快速判斷,好比相同的節點、互爲兄弟節點、某個節點是document或節點已經失去鏈接,這些狀況能夠先作判斷
if ( a === b ) { hasDuplicate = true; return 0; // 沒有父母的節點或者是document節點或斷開鏈接的節點(失聯的節點沒有parentNode) } else if ( !aup || !bup ) { return a === doc ? -1 : b === doc ? 1 : aup ? -1 : bup ? 1 : 0; // 若是是兄弟節點,則快速檢測 } else if ( aup === bup ) { return siblingCheck( a, b ); }
其餘狀況只能是從DOM的根節點開始判斷a和b屬於那個分支,比較分支的前後便可。這裏面jQuery作的很是巧妙,將a和b的祖先節點分別壓入ap和bp。注意壓入順序是將越是靠近根節點的祖先節點放在數組的最前面。
// 不然,咱們須要他們的祖先比較完整列表 cur = a; while ( (cur = cur.parentNode) ) { ap.unshift( cur ); } cur = b; while ( (cur = cur.parentNode) ) { bp.unshift( cur ); }
結果造成的ap/bp爲以下
ap = [#document節點,html節點,body節點,...]
bp = [#document節點,html節點,body節點,...]
從ap和bp的前半部分都是相同的,從某個下標index開始,ap[index]和bp[index]不一樣。很明顯,ap[index]和bp[index]是兄弟節點,咱們只須要比較ap[index]和bp[index]的誰在前,那麼a和b中誰就在前。那麼,判斷的代碼應該以下
// 從樹根節點開始往下找差別 while ( ap[i] === bp[i] ) { i++; } return i ? // 若是節點有一個共同的祖先,作一個同級檢查 siblingCheck( ap[i], bp[i] ) :0;
可是這裏還存在一個問題:若是a或b再也不文檔內呢?因此這裏還要加上這個部分的判斷代碼。因此最終這個對比函數的源碼爲
function( a, b ) { var cur, i = 0, aup = a.parentNode, bup = b.parentNode, ap = [ a ], bp = [ b ]; if ( a === b ) { hasDuplicate = true; return 0; // 沒有父母的節點或者是document節點或斷開鏈接的節點(失聯的節點沒有parentNode) } else if ( !aup || !bup ) { return a === doc ? -1 : b === doc ? 1 : aup ? -1 : bup ? 1 : 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.unshift( cur ); } // 從樹根節點開始往下找差別 while ( ap[i] === bp[i] ) { i++; } return i ? // 若是節點有一個共同的祖先,作一個同級檢查 siblingCheck( ap[i], bp[i] ) : // 不然節點在咱們的文檔內的排在前面 ap[i] === preferredDoc ? -1 : bp[i] === preferredDoc ? 1 : 0; }
Sizzle.getText(elem):獲取節點elem下的全部文本內容【包括標籤之間的空白】
參數elem能夠是一個節點,也能夠是及節點數組。
這部分代碼比較簡單。主要注意的是兼容性問題。docElem.textContent獲取內容是正確的,可是並不是全部的瀏覽器都支持該方法,好比IE8-。而IE支持的innerText方法有問題:該方法會自動給兩個標籤之間加空格。
好比
<ul id="myList"><li id="item1">Coffee<div>sdddddddddd</div></li><li id="item2">Tea</li></ul>
<script> function myFunction() { var lst = document.getElementById("myList"); var x = lst.textContent ;//CoffeesddddddddddTea
var y =lst.innerText;//Coffee sdddddddddd Tea }
myFunction();
</script>
因此,不能使用innerText來替代。沒辦法,IE8-只能使用文本節點的docElem.nodeValue方法獲取單個文本節點了:將elem下全部文本節點的nodeValue相加。
完整源碼以下:
getText = Sizzle.getText = function( elem ) { var node, ret = "", i = 0, nodeType = elem.nodeType; if ( !nodeType ) { // 若是沒有nodeType, 這預計是一個數組 for ( ; (node = elem[i]); i++ ) { // 不通過註釋節點 ret += getText( node ); } //element、document、DocumentFragment節點 } else if ( nodeType === 1 || nodeType === 9 || nodeType === 11 ) { // 使用textContent來獲取 // 使用innerText屬性刪除了新線的一致性 (see #11153) if ( typeof elem.textContent === "string" ) { return elem.textContent; } else { // 遍歷其子節點 for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) { ret += getText( elem ); } } //Text、CDATASection(不會由解析器解析的文本) } else if ( nodeType === 3 || nodeType === 4 ) { return elem.nodeValue; } //不包括註釋或處理指令節點 return ret; };
Sizzle.isXML(elem):判斷DOM節點是否位於XML文檔中,或者其自己就是XML文檔
該函數主要用於判斷指定文檔是一個XML文檔仍是一個HTML(或XHTML)文檔。這個判斷比較簡單,直接附上源碼
Sizzle.isXML = function( elem ) { // documentElement的判斷方式在他不存在的時候是可靠的 // (如在IE瀏覽器加載內置iframe- #4833) var documentElement = elem && (elem.ownerDocument || elem).documentElement; return documentElement ? documentElement.nodeName !== "HTML" : false; };
Sizzle.matches( expr, elements):篩選出elements知足CSS選擇器表達式expr的節點【最終返回的是節點數組】。
實現比較簡單啦,上源碼
Sizzle.matches = function( expr, elements ) { return Sizzle( expr, null, null, elements ); };
Sizzle.matchesSelector(elem, expr):判斷dom元素elem是否匹配CSS選擇器表達式expr
現代瀏覽器(IE9+,firefox,chrome等)都支持原生的docElem.matchesSelector,只不過帶上來各自對前綴。OK哪就簡單了,儘可能使用瀏覽器原生的方法,若是不行再使用Sizzle()方法從備選種子elem中獲取知足表達式expr的結果來判斷。須要注意的是IE9雖然也支持msMatchesSelector,可是在失聯的節點上會返回false,因此此時也使用Sizzle來處理。源碼以下
Sizzle.matchesSelector = function( elem, expr ) { // Set document vars if needed if ( ( elem.ownerDocument || elem ) !== document ) { setDocument( elem ); } //rattributeQuotes = /\=[\x20\t\r\n\f]*([^'"\]]*)[\x20\t\r\n\f]*\]/g // 確保屬性選擇器正確 expr = expr.replace( rattributeQuotes, "='$1']" ); //rbuggyMatches = /(?:)/ //rbuggyQSA老是包含 :focus,因此沒有必要作存在確認 if ( support.matchesSelector && !documentIsXML && (!rbuggyMatches || !rbuggyMatches.test(expr)) && !rbuggyQSA.test(expr) ) { try { var ret = matches.call( elem, expr ); // IE9 matchesSelector在斷開鏈接(再也不document上)節點會返回false if ( ret || support.disconnectedMatch || //同時,在IE9上,斷開鏈接的節點被認爲是一個文檔片斷 elem.document && elem.document.nodeType !== 11 ) { return ret; } } catch(e) {} } return Sizzle( expr, document, null, [elem] ).length > 0; };
若是以爲本文還有那麼一點點做用,請頂一下。