天天看点

jQuery源码中的一些细节分析(二)——透过兼容性代码看getElementsBy*在各个浏览器下的表现。

jQuery在自己内含的Sizzle引擎里大量使用了原生的

getElementsBy*

系列的函数来实现选择器,因为原生函数的效果足够好所以不需要额外的封装。但是原生的函数在不同的浏览器中表现又大不相同,也就产生了很多兼容性的问题,jQuery为了能够兼容这些行为怪异的浏览器(IE:你们都看我干吗)做出了很多努力,下面我们通过这些代码来看一看

getElementsBy*

在各个浏览器中的表现情况,以及如何做出兼容处理。

getElementsById

在这一部分中,jQuery写的兼容性代码最多。主要问题出现在IE6-7的版本上,这两个版本的浏览器在运行

getElementsById

时会沿着DOM树查找直到碰到第一个

name

属性或者

id

属性等于要查找值的元素……这个脑回路没谁了。

所以主要的兼容思路就是用

getElementsByName

来校验结果,如果发现

getElementsByName

getElementsById

的结果一样就说明这个浏览器的执行逻辑不符合我们的预期,所以我们需要将搜索开始位置重定位到当前的元素位置接着调用

getElementsById

方法然后接着校验就可以了。

support.getById = assert(function( el ) {
	docElem.appendChild( el ).id = expando;
	return !document.getElementsByName || !document.getElementsByName( expando ).length;
});

// ID filter and find
if ( support.getById ) {
	Expr.filter["ID"] = function( id ) {
		var attrId = id.replace( runescape, funescape );
		return function( elem ) {
			return elem.getAttribute("id") === attrId;
		};
	};
	Expr.find["ID"] = function( id, context ) {
		if ( typeof context.getElementById !== "undefined" && documentIsHTML ) {
			var elem = context.getElementById( id );
			return elem ? [ elem ] : [];
		}
	};
} else {
	//属性节点是作为叶子节点接入DOM树的
	Expr.filter["ID"] =  function( id ) {
		var attrId = id.replace( runescape, funescape );
		return function( elem ) {
			var node = typeof elem.getAttributeNode !== "undefined" &&
				elem.getAttributeNode("id");
				return node && node.value === attrId;
		};
	};

	// Support: IE 6 - 7 only
	// getElementById is not reliable as a find shortcut
	// 不可靠是因为也会查找name
	Expr.find["ID"] = function( id, context ) {
		if ( typeof context.getElementById !== "undefined" && documentIsHTML ) {
			var node, i, elems,
			elem = context.getElementById( id );

			if ( elem ) {

				// Verify the id attribute
				node = elem.getAttributeNode("id");
				if ( node && node.value === id ) {
					return [ elem ];
				}

				// Fall back on getElementsByName
				elems = context.getElementsByName( id );
				i = 0;
				while ( (elem = elems[i++]) ) {
					node = elem.getAttributeNode("id");
					if ( node && node.value === id ) {
						return [ elem ];
					}
				}
			}

			return [];
		}
	};
}
           

getElementsByClassName

在jQuery中对这个方法的兼容性测试很简单只是使用了下面的代码来检测了它是否为native code。如果不是native code就直接不采用这种方法。看来有些版本的浏览器内核(IE<9)应该是根本没有对getElementsByClassName进行支持。

support.getElementsByClassName = rnative.test( document.getElementsByClassName );
           

native code

有些函数如果我们打印出来的话函数体的部分会显示[native code],意思就是指这个函数的函数体已经是二进制的代码,我们只能通过指定结构(函数名)来调用它,不能改写也不能使用bing,call,apply等方法。jQuery中的rnative正则式就是用来检验一个函数是否是native code的。我觉得jQuery之所以会检查native code是想避免错误的引用用户定义的或者其他库定义的同名函数。

getElementsByTagName

我们先来看jQuery是如何验证这个函数的兼容性的:

support.getElementsByTagName = assert(function( el ) {
	el.appendChild( document.createComment("") );
	return !el.getElementsByTagName("*").length;
});
function assert( fn ) {
	var el = document.createElement("fieldset");

	try {
		return !!fn( el );
	} catch (e) {
		return false;
	} finally {
		// Remove from its parent by default
		if ( el.parentNode ) {
			el.parentNode.removeChild( el );
		}
		// release memory in IE
		el = null;
	}
}
           

这一段的代码其实是验证了getElementsByTagName的返回类型中是否包含了注释节点。

关于返回类型

W3C的标准要求该方法返回nodelist类型,但是IE浏览器的nodelist继承了Collection类使得自己的nodelist和其他人的不太一样。所以支持的方法有很多不一样的地方,使用这个方法之前一定要确定兼容性。

jQuery对他的兼容支持代码如下:

Expr.find["TAG"] = support.getElementsByTagName ?
		function( tag, context ) {
			if ( typeof context.getElementsByTagName !== "undefined" ) {
				return context.getElementsByTagName( tag );

			// DocumentFragment nodes don't have gEBTN
			} else if ( support.qsa ) {
				return context.querySelectorAll( tag );
			}
		} :

		function( tag, context ) {
			var elem,
				tmp = [],
				i = 0,
				// By happy coincidence, a (broken) gEBTN appears on DocumentFragment nodes too
				results = context.getElementsByTagName( tag );

			// Filter out possible comments
			if ( tag === "*" ) {
				while ( (elem = results[i++]) ) {
					if ( elem.nodeType === 1 ) {
						tmp.push( elem );
					}
				}

				return tmp;
			}
			return results;
		};