zoukankan      html  css  js  c++  java
  • 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选取存在的问题之后分析

  • 相关阅读:
    day 011总结
    day 010 总结
    day 10作业
    day 009总结
    day 008总结
    wireshark 解密tls消息
    js基础 数组slice
    js基础 数组splice
    js基础 Array.from
    js基础 Array.of
  • 原文地址:https://www.cnblogs.com/aaronjs/p/3847964.html
Copyright © 2011-2022 走看看