深入理解querySelector(All)


      querySelector和querySelectorAll同屬於 Selectors API Level 1規范,該規范早在2006年就已經開始發展,並在2007年10月形成querySelector(All)的雛形。由於規范發展的夠早,所以除了IE6、7以外,所有瀏覽器都基本支持。這兩個方法可以作用到Element、Document、DocumentFragment實例上面,即:
var elem = document.getElementById("test"),
      frag = document.createDocumentFragment();

frag.appendChild(document.createElement("div"));

elem.querySelector("p");// Element, querySelectorAll

document.querySelector("div");// Document, querySelectorAll
document.body.querySelector("div");// querySelectorAll

frag.querySelector("div");// documentFragment, querySelectorAll

      方法接收唯一的一個參數,該參數可為任意合法的CSS選擇器字符串。不過在IE8下,對於大部分的CSS3選擇器都不支持(只支持相鄰兄弟element1~element2;屬性選擇器[attr^=val][attr$=val],[attr*=val])。除此之外,如果想要在IE8下使用偽元素選擇器,需要用:,而不是CSS3規定的::(css3選擇器的瀏覽器支持參考:http://caniuse.com/#search=nth-of-type)。

      Selectors API返回的內容是靜態的NodeList,而非實時更新的NodeList,這和get系列(早期的chrome等瀏覽器返回的是NodeList,現在已經改為HTMLCollection實例。NodeList和HTMLCollection最大的不同就是NodeList可包括文本、注釋等非元素節點,而HTMLCollection只包括元素節點)、document.images返回動態的集合(HTMLCollection)以及childNodes(NodeList)是不一樣的。

 

      Selectors API雖然好用,不過在使用的時候還是需要注意一些問題。以下面代碼為例:

<body>
<div id="test"><p>test</p></div>
</body>
var ele = document.getElementById("test");
ele.querySelector("div p").length; // A
jQuery(ele).find("div p").length; // B
ele.querySelector("body p").length; // C
jQuery(ele).find("body p").length; // D

       對於代碼A,返回值為1,;代碼B,返回值為0。代碼C,返回值仍為1;代碼D,返回值為0。(結果適用於所有支持Selectors API的瀏覽器)

      對於習慣使用jQuery的人來說,上面的結果可能有點接受不了。不過在規范中明確寫明:

Even though the method is invoked on an element, selectors are still evaluated in the context of the entire document. In the following example, the method will still match the div element's child p element, even though the body element is not a descendant of the div element itself.

var div = document.getElementById("bar");
var p = div.querySelector("body p");
      一句話,Selectors API選擇器的查找范圍仍舊是document,只不過在查找完畢之后會判斷元素是否位於ele的子樹中。elem.querySelector(All)(str)相當於document.querySelector(All)(str)和elem子樹的交集。
      個人猜想,W3C設計Selectors API的初衷就是為了達到和在CSS樣式表中寫相同的css Selector選中的元素完全相同的效果。而在樣式表中,並沒有作用范圍的概念,所有css選擇器的“作用域”都是整個文檔(:scope除外,該偽元素可以指定css選擇器的作用范圍。目前IE、opera不支持)。
 
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
 
      google了一下,翻到了jQuery作者在08年關於這個問題的一篇文章( http://ejohn.org/blog/thoughts-on-queryselectorall)。 John Resig和Prototype、Dojo的作者都認為Selector API沒有作用范圍是個明顯的錯誤。文章里面還有john和規范制定者的一些關於這個問題的來往郵件,有興趣的話可以看一下( http://lists.w3.org/Archives/Public/public-webapi/2008Apr/thread.html#msg251)。
 
      我想,這也是w3c制定 Selectors API Level 2的原因。在這個規范中,提出了find、findAll方法,實際上就和jQuery的find方法效果一致了,這兩個方法目前還沒有瀏覽器支持。除此之外,還有一個matches方法,用於判斷元素是否和選擇器匹配,該方法在各個瀏覽器的實現為( https://developer.mozilla.org/en-US/docs/Web/API/Element.matches):
      
 
      在jQuery1.4.2以及之前的版本的Sizzle實現中,只使用了document.querySelectorAll這個方法。在1.4.3以后開始使用elem.querySeectorAll。為了避免作用范圍的問題,jQuery在使用該方法時先定了必須是元素才可使用該方法,然后首先把該元素的原有id存到一個變量里面,接着將元素ID設為"__sizzle__",最后在選擇器中手動添加ID來限定選擇范圍。操作完之后再把ID設為原來的值(原來沒有ID則直接remove掉)。代碼如下:
                                // qSA works strangely on Element-rooted queries
                // We can work around this by specifying an extra ID on the root
                // and working up from there (Thanks to Andrew Dupont for the technique)
                // IE 8 doesn't work on object elements
                } else if ( context.nodeType === 1 && context.nodeName.toLowerCase() !== "object" ) {
                    var old = context.id, id = context.id = "__sizzle__";

                    try {
                        return makeArray( context.querySelectorAll( "#" + id + " " + query ), extra );

                    } catch(pseudoError) {
                    } finally {
                        if ( old ) {
                            context.id = old;

                        } else {
                            context.removeAttribute( "id" );
                        }
                    }
                }

     不過,如果文檔中真的有的元素id為“__sizzle__”,這個方法應該就會悲劇了。

 

     在zepto中,並沒有針對elem使用querySelector(All)時的特殊處理,So:

<!DOCTYPE html>
<html>
<head>
<script src="http://zeptojs.com/zepto.min.js"></script>
  <meta charset="utf-8">
  <title>JS Bin</title>
</head>
<body>
  <div id="a"><div><p></p></div></div>
</body>
<script>
var ele = Zepto("#a").find("body div div p");
alert(ele.length); // 1
</script>
</html>

 

希望zepto可以盡快修改這個問題,以求達到從jQuery到Zepto更好的遷移。
 
這個問題很多人早就發現了,這有篇文章,也是說的這個問題:
這篇文章寫的稍微有點不對,就是在所有瀏覽器下的NodeList實例(childNodes、querySelectorAll)是沒辦法使用query方法的:
<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <title>JS Bin</title>
</head>
<body>
  <div id="a"><div><p></p></div></div>
</body>
<script>
var ele =document.getElementById("a").querySelectorAll("*");
var chd = document.getElementById("a").childNodes;
alert(ele.querySelectorAll); // undefined
alert(chd.querySelectorAll); // undefined
</script>
</html>

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM