JavaScript框架设计
上QQ阅读APP看书,第一时间看更新

6.4.2 contains

contains方法就是判定参数1是否包含参数2。这通常用于优化。比如在早期的Sizzle,对于#aaa p.class这个选择符,它会优先用getElementsByClassName或getElementsByTagName取种子集,然后就不继续往左走了,直接跑到最左的#aaa,取得#aaa元素,然后通过contains方法进行过滤。随着Sizzle的体积进行增大,它现在只剩下另一个关于ID的用法,即,如果上下文对象非文档对象,那么它会取得其ownerDocument,这样就可以用getElementById,然后利用contains方法进行验证!

      //Sizzle 1.10.15
      if  (context.ownerDocument  &&  (elem  =  context.ownerDocument.getElementById(m))  &&
      contains(context, elem) && elem.id === m) {
          results.push(elem);
          return results;
      }

我们再看看contains的实现。

      //Sizzle 1.10.15
      var rnative = /^[^{]+\{\s*\[native \w/,
      hasCompare = rnative.test( docElem.compareDocumentPosition ),
      contains = hasCompare || rnative.test(docElem.contains) ?
              function(a, b) {
                  var adown = a.nodeType === 9 ? a.documentElement : a,
                          bup = b && b.parentNode;
                  return a === bup || !!(bup && bup.nodeType === 1 && (
                          adown.contains ?
                          adown.contains(bup) :
                          a.compareDocumentPosition && a.compareDocumentPosition(bup) & 16
                          ));
              } :
              function(a, b) {
                  if (b) {
                      while ((b = b.parentNode)) {
                          if (b === a) {
                              return true;
                          }
                      }
                  }
                  return false;
              };

它自己做了预判定,但这时如果传入XML元素节点,可能就会出错。因此建议改成实时判定,虽然每次进入都判定一次使用哪个原生API。下面是mass Framework的实现。

      contains = function(a, b, same) {
          // 第一个节点是否包含第二个节点, same允许两者相等
          if (a === b) {
              return !!same;
          }
          if(!b.parentNode)
              return false;
          if (a.contains) {
              return a.contains(b);
          } else if (a.compareDocumentPosition) {
              return !!(a.compareDocumentPosition(b) & 16);
          }
          while ((b = b.parentNode))
            if (a === b) return true;
          return false;
      }

现在来解释一下contans与compareDocumentPosition这两个API。contains原来是IE的私有实现,后来其他浏览器也借鉴这方法,如Firefox在9.0也装了此方法。它是一个元素节点的方法,如果另一个等于或包含于它的内部,就返回 true。compareDocumentPosition 是 DOM Level 3 specification定义的方法,Firefox等标准浏览器都支持,它用于判定两个节点的关系,而不单只是包含关系。这里是从 NodeA.compareDocumentPosition(NodeB) 返回的结果,包含你可以得到的信息,如表6.2所示。

表6.2

有时候,两个元素的位置关系可能连续满足上表的两种情况,比如A包含B,并且A在B的前面,那么compareDocumentPosition就返回20。

由于旧版本IE不支持compareDocumentPosition,因此jQuery的作者 John Resig写了个兼容函数,用到IE的另一个私有实现sourceIndex。sourceIndex会根据元素的位置从上到下,从左到右依次加1,比如HTML标签的sourceIndex为0,HEAD标签的为1,BODY标签为2,HEAD的第一个子元素为3……如果元素不在DOM树,那么返回−1。

      // Compare Position - MIT Licensed, John Resig
      function comparePosition(a, b) {
          return a.compareDocumentPosition ? a.compareDocumentPosition(b) :
          a.contains ? (a != b && a.contains(b) && 16)+
              (a != b && b.contains(a) && 8)+
              (a.sourceIndex >= 0 && b.sourceIndex >= 0 ?
                (a.sourceIndex < b.sourceIndex && 4)+
                (a.sourceIndex > b.sourceIndex && 2): 1): 0;
      }