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; }