jQuery Sizzle选择器(三)
在Sizzle的入口方法Sizzle()中看到的一个根据浏览器来初始化document各个方法的函数setDocument(),接下来主要看一下这个方法都做了什么。
但之前有必要看一下它用到的一些Sizzle内部使用的方法:
// 通过创建一个div元素,检测被传入的fn是否被当前浏览器支持
function assert( fn ) { var div = document.createElement("div"); //此处用try-catch的原因是:被传入的fn很有可能是会报错的。因为fn中用的方法或属性很可能不被当前浏览器所支持。 try {
// 尝试执行被传入的方法,并将结果返回,如果出错则走catch,直接返回false(当前方法不被支持) return !!fn( div ); } catch (e) { return false; } finally { // 如果被创建的div存在parentNode,则将当前div删除。
// 这里div看上去不可能有parentNode,因为感觉它没有被插入DOM树。但由于被传入的函数fn很有可能会将这个div插入DOM树,所有必须执行此操作。 if ( div.parentNode ) { div.parentNode.removeChild( div ); } // 将div的引用置空,方便回收 div = null; } }
// 主要功能是根据各个浏览器对原生方法的支持情况对当前浏览器是否支持某方法进行判别
setDocument = Sizzle.setDocument = function( node ) {
// node是入口函数中传入的context。例如:$( "a", b )。 这里的node指的就是b
// 如果传入了node,则拿到node所在的document,如果node没有ownerDocument,说明它本身就是document; 如果没有传入node,则使用preferredDoc(它的值是window.document) var doc = node ? node.ownerDocument || node : preferredDoc,
// 拿到当前文档的父文档??? parent = doc.defaultView; // ??? if ( doc === document || doc.nodeType !== 9 || !doc.documentElement ) { return document; } // Set our document document = doc; docElem = doc.documentElement; // 判断当前document是不是HTML documentIsHTML = !isXML( doc );
// 如果存在父文档,并且父文档存在attachEvent方法(说明是IE),并且父文档不是最顶层??奇怪。。
// 因为IE8及IE8-都不支持defaultView,所以此处会过滤掉IE8-( jQuery2.0不再支持IE8及以下版本 )
// IE9+ 在parent被卸载前setDocument。呃。。晕了。。 if ( parent && parent.attachEvent && parent !== parent.top ) { parent.attachEvent( "onbeforeunload", function() { setDocument(); }); }
// 判断getAttribute方法是否能够拿到元素的属性,难道有哪些浏览器是不能通过getAttribute拿到元素属性的吗?呃。。 support.attributes = assert(function( div ) { div.className = "i"; return !div.getAttribute("className"); });
// 判断getEBTN是否只返回元素节点(某些浏览器使用该方法会返回元素节点和注释节点) support.getElementsByTagName = assert(function( div ) { div.appendChild( doc.createComment("") ); return !div.getElementsByTagName("*").length; }); // 判断当前浏览器是否支持gEBCN方法。 support.getElementsByClassName = assert(function( div ) {
// 在assert方法中创建的div中插入一串DOM。 div.innerHTML = "<div class='a'></div><div class='a i'></div>";
// 给第一个子元素(class为a的元素)的class设置为“i”(不明白为什么要做这一步) div.firstChild.className = "i"; // 调用gEBCN方法,看class为“i”的元素是不是返回两个(不明白为什么非要判断有两个,有一个不行?) return div.getElementsByClassName("i").length === 2; }); // 此处会将assert中创建的div插入DOM树。所以assert中才需要将其从DOM中删除
// 作用是判断当前浏览器通过gEBI方法返回的是否是单纯的ID为相应值得元素(某些浏览器使用gEBI方法可能会返回属性name为对应值得元素) support.getById = assert(function( div ) {
// 将assert中创建的这个div插入DOM树并将它的id设置为expando(一个Sizzle自己生成的以“sizzle”开头的很长的字符串)。目的是不希望DOM中用户有设置的相同的id docElem.appendChild( div ).id = expando;
// 如果gEBN方法不存在,或者使用gEBN方法取不到上面插入DOM的div,则说明使用gEBI方法只会查找ID为对应值得元素返回。 return !doc.getElementsByName || !doc.getElementsByName( expando ).length; }); // 如果gEBI确实只会检查ID,而不是连NAME一起检查的话,初始化Expr的find.ID方法和filter.ID方法 if ( support.getById ) {
// 这里的context应该在调用该方法时会传入document吧! Expr.find["ID"] = function( id, context ) {
// 如果context上有gEBI方法,并且该文档是HTML if ( typeof context.getElementById !== strundefined && documentIsHTML ) {
// 调用gEBI方法找到有相应ID的元素 var m = context.getElementById( id ); // 某些浏览器会将文档碎片中的带有相应id的元素找到,因此需要看看该元素是否有父节点。(但是只检查父节点就可以排除吗?) return m && m.parentNode ? [m] : []; } };
// 过滤出id为相应ID的元素 Expr.filter["ID"] = function( id ) { var attrId = id.replace( runescape, funescape ); return function( elem ) { return elem.getAttribute("id") === attrId; }; };
// 否则的话,说明使用gEBI并不一定只是单纯的拿到ID为对应值的元素 } else { // 删除掉绑定Expr的find上的ID方法 delete Expr.find["ID"]; // 给Expr的filter绑定ID方法 Expr.filter["ID"] = function( id ) { var attrId = id.replace( runescape, funescape ); return function( elem ) {
// 通过拿到当前元素的ID var node = typeof elem.getAttributeNode !== strundefined && elem.getAttributeNode("id");
// 判断当前元素的ID值是否和相应id值相等 return node && node.value === attrId; }; }; }
// 为Expr的find绑定TAG方法,如果gEBTN方法只返回元素节点。 Expr.find["TAG"] = support.getElementsByTagName ?
// 则将该函数赋值给TAG,当调用TAG方法时会使用gEBTN方法返回想要查找的元素 function( tag, context ) { if ( typeof context.getElementsByTagName !== strundefined ) { return context.getElementsByTagName( tag ); } } :
// 否则的话绑定该方法 function( tag, context ) { var elem, tmp = [], i = 0,
// 拿到一个包含注释节点的元素数组 results = context.getElementsByTagName( tag );
// 如果tag是* if ( tag === "*" ) {
// 通过遍历结果集中的所有元素 while ( (elem = results[i++]) ) {
// 只将这些元素中nodeType为1的推入临时数组中 if ( elem.nodeType === 1 ) { tmp.push( elem ); } } // 返回这个临时数组 return tmp; }
// 如果tag不是*,则直接返回结果集即可。 return results; }; // 为Expr的find绑定CLASS方法,如果浏览器支持该方法则为其绑定方法,否则为undefined Expr.find["CLASS"] = support.getElementsByClassName && function( className, context ) {
// 如果浏览器支持该方法,并且该文档是HTML文档 if ( typeof context.getElementsByClassName !== strundefined && documentIsHTML ) {
// 直接调用gEBCN方法返回结果 return context.getElementsByClassName( className ); } }; rbuggyMatches = []; rbuggyQSA = []; // 检测浏览器是否支持高级的qSA方法,并同时为support.qsa赋值。完全不了解qSA,该部分先略过。。。。 if ( (support.qsa = rnative.test( doc.querySelectorAll )) ) { // 调用assert方法判断qSA的支持情况。具体做这些的意义不清楚。 assert(function( div ) { div.innerHTML = "<select><option selected=''></option></select>"; // 估计是各个浏览器对qSA查找selected和checked元素有差异。 if ( !div.querySelectorAll("[selected]").length ) { rbuggyQSA.push( "\\[" + whitespace + "*(?:value|" + booleans + ")" ); } if ( !div.querySelectorAll(":checked").length ) { rbuggyQSA.push(":checked"); } }); assert(function( div ) { var input = doc.createElement("input"); input.setAttribute( "type", "hidden" ); div.appendChild( input ).setAttribute( "t", "" ); if ( div.querySelectorAll("[t^='']").length ) { rbuggyQSA.push( "[*^$]=" + whitespace + "*(?:''|\"\")" ); } if ( !div.querySelectorAll(":enabled").length ) { rbuggyQSA.push( ":enabled", ":disabled" ); }
div.querySelectorAll("*,:x"); rbuggyQSA.push(",.*:"); }); } if ( (support.matchesSelector = rnative.test( (matches = docElem.webkitMatchesSelector || docElem.mozMatchesSelector || docElem.oMatchesSelector || docElem.msMatchesSelector) )) ) { assert(function( div ) { // Check to see if it's possible to do matchesSelector // on a disconnected node (IE 9) support.disconnectedMatch = matches.call( div, "div" ); // This should fail with an exception // Gecko does not error, returns false instead matches.call( div, "[s!='']:x" ); rbuggyMatches.push( "!=", pseudos ); }); } rbuggyQSA = rbuggyQSA.length && new RegExp( rbuggyQSA.join("|") ); rbuggyMatches = rbuggyMatches.length && new RegExp( rbuggyMatches.join("|") ); // contains在Sizzle()的作用域中定义,此处为其赋值
// 首先判断当前浏览器是否支持contains或者cDP(compareDocumentPosition)方法
// 原生的contains有个问题是如果a===b,调用contains依然会返回true contains = rnative.test( docElem.contains ) || docElem.compareDocumentPosition ?
// 如果支持这两个方法其中的一个 function( a, b ) {
// 如果a是document,拿到documentElement节点,否则a就是a 。。呃。。 var adown = a.nodeType === 9 ? a.documentElement : a,
// 拿到b的父节点 bup = b && b.parentNode;
// 如果a是b的父节点。
// 如果b存在父节点并且父节点是元素节点的话:如果存在contains方法,调用contains方法;否则就是存在cDP方法,那就调用CDP方法。
// 增加了这些判断,就是为了将原生js中contains方法的a===b返回true的问题解决。
return a === bup || !!( bup && bup.nodeType === 1 && ( adown.contains ? adown.contains( bup ) : a.compareDocumentPosition && a.compareDocumentPosition( bup ) & 16 )); } :
// 如果浏览器不支持上面的两个方法,那就自己写简单的。(这个简单的也没啥问题啊?那为啥还用原生的?做那么多判断还。。)
// 这个简单的contains就是拿到b的父节点,判断父节点是否和a相等。如果相等就返回true。
// 否则就那父节点的父节点和a相比较。直到没有父节点位置。如果始终没有和a相等的b的祖先元素。则返回false function( a, b ) { if ( b ) { while ( (b = b.parentNode) ) { if ( b === a ) { return true; } } } return false; }; // 检测元素的位置关系
// 看浏览器是否支持cDP方法 sortOrder = docElem.compareDocumentPosition ?
// 如果支持cDP方法,将该方法赋值给sortOrder function( a, b ) { // 如果a等于b,说明是完全相同的元素, if ( a === b ) { hasDuplicate = true;
// 返回0 return 0; } // 如果a、b都支持cDP方法,调用cDP方法得到a和b的关系值 var compare = b.compareDocumentPosition && a.compareDocumentPosition && a.compareDocumentPosition( b ); // 如果a和b的关系值存在的话 if ( compare ) { // 没看懂 if ( compare & 1 || (!support.sortDetached && b.compareDocumentPosition( a ) === compare) ) { // 如果a是document 或者document包含a if ( a === doc || contains(preferredDoc, a) ) {
return -1; }
// 如果b是document 或者document包含b if ( b === doc || contains(preferredDoc, b) ) { return 1; } // 呃。。。。 return sortInput ? ( indexOf.call( sortInput, a ) - indexOf.call( sortInput, b ) ) : 0; } return compare & 4 ? -1 : 1; } // return a.compareDocumentPosition ? -1 : 1; } :
// 如果浏览器不支持cDP方法,将该方法赋值给sortOrder function( a, b ) { var cur, i = 0, aup = a.parentNode, bup = b.parentNode, ap = [ a ], bp = [ b ]; // 如果a等于b,说明是相同的元素,返回0 if ( a === b ) { hasDuplicate = true; return 0; // 如果a不存在父节点或者b不存在父节点,说明其中一个是document节点 } else if ( !aup || !bup ) {
// a是document节点,返回-1 return a === doc ? -1 :
// 如果b是document节点,返回1 b === doc ? 1 :
// 如果a存在父节点,返回-1 aup ? -1 :
// 如果b存在父节点,返回1 bup ? 1 :
// ??? sortInput ? ( indexOf.call( sortInput, a ) - indexOf.call( sortInput, b ) ) : 0; // 如果a和b有相同的父元素 } else if ( aup === bup ) {
// 调用sC方法判断兄弟元素的先后顺序(猜测) return siblingCheck( a, b ); } cur = a;
// 如果a存在父元素,就把父元素赋值给cur,一直到a的最外层祖先元素 while ( (cur = cur.parentNode) ) {
// 并且每一次都把cur加到ap数组的开头,这样就形成一个a的所有祖先元素的数组 ap.unshift( cur ); } cur = b;
// 对b执行像上一步中a一样的处理,形成一个b的所有祖先元素的数组 while ( (cur = cur.parentNode) ) { bp.unshift( cur ); } // 从最外层祖先元素开始遍历a和b的祖先元素数组。依次往里,知道找到开始不同的那一组祖先元素,并得到这个数组index while ( ap[i] === bp[i] ) { i++; } // 如果存在一个这样的i return i ? // 判断这两个开始不同的祖先元素的先后关系 siblingCheck( ap[i], bp[i] ) : // 如果a的这个祖先元素时document,则返回-1 ap[i] === preferredDoc ? -1 :
// 如果b的这个祖先元素是document,返回1 bp[i] === preferredDoc ? 1 :
// 否则返回0 0; }; // 修成正果,返回一个当前浏览器可用的document return doc; };