1. 程式人生 > >sizzle分析記錄:關於querySelectorAll相容問題

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限制範圍

image

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選取存在的問題之後分析