sizzle分析記錄:關於querySelectorAll相容問題
querySelector和querySelectorAll是W3C提供的 新的查詢介面
目前幾乎主流瀏覽器均支援了他們。包括 IE8(含) 以上版本、 Firefox、 Chrome、Safari、Opera。
萬能的sizzle在高版本的瀏覽器中複雜的選擇器儘量走querySelectorAll,前提是這個匹配的節點沒有相容問題
從IE8開始雖然支援querySelectorAll的API,但是會有各式各樣的BUG,所以sizzle拿rbuggyQSA用來記錄這個BUG問題
if ( support.qsa && (!rbuggyQSA || !rbuggyQSA.test( selector )) ) {//newContext.querySelectorAll( newSelector ) }
zepto的選擇器則更直接
zepto.qsa = function(element, selector) { var found return (isDocument(element) && idSelectorRE.test(selector)) ? ((found = element.getElementById(RegExp.$1)) ? [found] : []) : (element.nodeType !== 1 && element.nodeType !== 9) ? [] : slice.call( classSelectorRE.test(selector)? element.getElementsByClassName(RegExp.$1) : tagSelectorRE.test(selector) ? element.getElementsByTagName(selector) : element.querySelectorAll(selector) ) }
sizzle 2000行,zepto直接querySelectorAll介面,利弊各自評估了
重點就看querySelectorAll的坑到底有哪些?
介面定義:
partial interface Document { Element? querySelector(DOMString selectors); NodeList querySelectorAll(DOMString selectors); }; partial interface DocumentFragment { Element? querySelector(DOMString selectors); NodeList querySelectorAll(DOMString selectors); }; partial interface Element { Element? querySelector(DOMString selectors); NodeList querySelectorAll(DOMString selectors); };
從介面定義可以看到Document、DocumentFragment、Element都實現了NodeSelector介面。即這三種類型的元素都擁有者兩個方法。
querySelector和querySelectorAll的引數須是符合 css selector 的字串。
不同的是querySelector返回的是一個物件,querySelectorAll返回的一個集合(NodeList)。
所以選擇querySelectorAll更符合jQuery這個合集物件的習慣
document.querySelectorAll 與 element.querySelectorAll區別?
當呼叫上下為document的時候,沒有什麼問題,各瀏覽器的實現基本一致
如果呼叫的上下文是element,dom Node的時候,瀏覽器的實現有點不同
具體就是表現在:element.querySelectorAll 在文件內找全部符合選擇器描述的節點包括Element本身
<div class= "aaron" id= "aaronId" > <p><span>內容</span></p> <div class="text">452</div> </div>
js
<script type="text/javascript"> var aaElement = document.getElementById('aaronId'); var element = aaElement.querySelector('.aaron span'); var elementList = document.querySelectorAll('.aaron span'); console.log(element); // <span>Test</span> console.log(elementList); //
問題出在testElement.querySelector盡然還有返回值!選擇上下文是在aaElement裡面,選擇器是.aaarn就父節點,理論是找不到對應的節點的
所以邏輯上是不合理的,因為根本找不到,但是結果跟document呼叫如出一轍,所以此時node ele類似document 了
可能的查詢機制是這樣的:首先在document的範圍內進行查詢所有滿足選擇器條件的元素,
在上面這段程式碼中,我們的選擇器是.aaron span,就是所有的直接父元素類名為aaron的元素。
然後,再看哪些元素是呼叫querySelector/querySelectorAll的元素的子元素,這些元素將會被返回
這也就說明了為什麼aaElement會一同返回
那麼針對這種情況如何相容?
程式設計師的智慧總是無窮的, Andrew Dupont提出了一個解決方案,來自jQuery2.1.1
先看看jQuery最終的實現newContext.querySelectorAll用的上下文呼叫
if ( newSelector ) { try { push.apply( results, newContext.querySelectorAll( newSelector ) ); return results; } catch(qsaError) { } finally { if ( !old ) { context.removeAttribute("id"); } } }
程式碼可見newContext可能是document || 提供的一個上下文
如:$("#aaronId").find("div[class='text']) 此時的上下文即是$("#aaronId")節點
jQuery(element).find(selector) 在文件內找全部符合選擇器描述的節點不包括Element本身
注意finally總是執行context.removeAttribute("id"),意味著我們在之前的處理強制加了一個id
反推hack的手法,selectors前面指定上下文的的id,限制匹配的範圍
版本各有實現的不同,但是我們目前最終版為標準2.1.1
IE 8 :不支援上下文為object;
if ( nodeType === 1 && context.nodeName.toLowerCase() !== "object" ) { groups = tokenize( selector ); if ( (old = context.getAttribute("id")) ) { nid = old.replace( rescape, "\\$&" ); } else { context.setAttribute( "id", nid ); } nid = "[id='" + nid + "'] "; i = groups.length; while ( i-- ) { groups[i] = nid + toSelector( groups[i] ); } newContext = rsibling.test( selector ) && testContext( context.parentNode ) || context; newSelector = groups.join(","); }
1. 關鍵是給context設定一個id
所以上下文content,就會存在這個id限制範圍
2. 拼接出查詢的選擇器,附上這個ID字首
newSelector: "[id='sizzle-1405486760710'] div[class='text']"
3. 查詢
newContext.querySelectorAll( newSelector )
4. 因為強制加了ID,所以需要刪除
context.removeAttribute("id");
這樣就達到目的範圍限制:context.querySelectorAll了
querySelectorAll在選擇器上存在的問題,具體我是看jQuery的原始碼相關處理,基本都是IE8上的問題
jQuery對相容的判斷,都是採用的功能判斷直接特性檢測,偽造一個真實的環境測試支援度
針對querySelectorAll選取存在的問題之後分析