1. 属性childNodes vs 属性children

childNodes Retrieves a collection of HTML Elements and TextNode objects that are direct descendants of the specified object.
标准,包含HTML元素、文本
children Retrieves a collection of DHTML Objects that are direct descendants of the object.
非标准,只包含HTML元素

 

2. 方法children vs 方法contents

jQuery.fn.children( elem ) 只包含Element
jQuery.fn.contents( elem ) 包含Element、Text、Comment

3. 完整源码分析(亮点在排序、去重部分)

jQuery.each({
	parent: function( elem ) { // 父元素
		var parent = elem.parentNode;
		return parent && parent.nodeType !== 11 ? parent : null; // DocumentFragment 11, DocumentType 10
	},
	parents: function( elem ) { // 祖先元素
		return jQuery.dir( elem, "parentNode" ); // 检索所有父元素,直至document
	},
	parentsUntil: function( elem, i, until ) { //
		return jQuery.dir( elem, "parentNode", until );
	},
	next: function( elem ) { // 下一个兄弟元素
		return jQuery.nth( elem, 2, "nextSibling" ); // 从1开始计数,当前是第1个,为什么要再搞一套不一致的索引方法呢?
	},
	prev: function( elem ) { // 上一个兄长元素
		return jQuery.nth( elem, 2, "previousSibling" );
	},
	nextAll: function( elem ) { // 所有的兄弟元素
		return jQuery.dir( elem, "nextSibling" );
	},
	prevAll: function( elem ) { // 所有的兄长元素
		return jQuery.dir( elem, "previousSibling" );
	},
	nextUntil: function( elem, i, until ) { // 所有的兄弟元素,但是不包括until
		return jQuery.dir( elem, "nextSibling", until );
	},
	prevUntil: function( elem, i, until ) { // 所有的兄长元素,但是不包括until
		return jQuery.dir( elem, "previousSibling", until );
	},
	siblings: function( elem ) { // 所有兄长、兄弟元素,不包括当前元素
		return jQuery.sibling( elem.parentNode.firstChild, elem ); // 父元素的第一个子元素的所有兄弟元素,排除当前元素elem
		// 取子元素childNodes然后过滤elem,效率更高
	},
	children: function( elem ) { // 所有的子节点,只包含Element
		return jQuery.sibling( elem.firstChild ); // 第一个子元素的所有兄弟元素
		// 为什么不直接取子元素呢?childNodes不是效率更高么?
	},
	contents: function( elem ) { // 所有的子节点,包含Element、Text、Comment
		return jQuery.nodeName( elem, "iframe" ) ?
			elem.contentDocument || elem.contentWindow.document : // 如果是iframe,则取document
			jQuery.makeArray( elem.childNodes ); // 将childNodes转换数组,childNodes是伪数组,转换后遍历数组时可以避免对childNodes进行检查,提高性能
	}
}, function( name, fn ) {
	jQuery.fn[ name ] = function( until, selector ) {
		var ret = jQuery.map( this, fn, until ), // 将this中的元素,用fn处理,最后返回真正的数组,until用于过滤
			// The variable 'args' was introduced in
			// https://github.com/jquery/jquery/commit/52a0238
			// to work around a bug in Chrome 10 (Dev) and should be removed when the bug is fixed.
			// http://code.google.com/p/v8/issues/detail?id=1050
			args = slice.call(arguments); // 将arguments复制并转为数组

		if ( !runtil.test( name ) ) { // 不以Until结尾
			selector = until; // 不需要参数until,只有一个参数selector,util只到这里为止
		}

		if ( selector && typeof selector === "string" ) {
			ret = jQuery.filter( selector, ret ); // 对ret数组用selector进行过滤,只留下匹配的元素
			// jQuery.filter会调用jQuery.find.matches > Sizzle.matches > Sizzle,Sizzle查找、过滤的结果已经经过排序、去重
		}

		/**
		 * 排序、去重
		 * 首先要知道this是经过排序、且无重复
		 * 如果长度大于1,才需要判断是否需要去重
		 * 如果name是 children contents next prev 之一,则不需要排序去重,因为这四个方法不会产生无序和重复的结果,为什么呢?
		 * 因为this中的元素是有序、去重的,所以的它子节点、兄弟、兄长,也都是有序、无重复的
		 * 但是parent parents parentsUntil nextAll prevAll nextUntil prevUntil siblings却有可能产生重复、无序的元素
		 *
		 * 可见这里进行了优化,不可否则这种编码习惯很精致很赞,但是:
		 * 即使不需要排序、去重的情况,也必然要经过这些漫长的判断,就是一种浪费
		 * 这种多功能导致的复杂性和无谓的性能消耗,在jQuery中随处可见
		 * 虽然没有做详尽的性能测试,其实也没必要,但稍微给我有点“机关算尽太聪明,枉费了卿卿性命”的感觉
		 * 从刚开始读源码时的惊艳和崇拜,渐渐的能分辨其中的精髓与作者的权衡拿捏,jQuery的伟大不可否认,
		 * 但是我们写代码的时候,要借鉴参考,也要思考取舍
		 *
		 * 上边的感触仅供参考,我还处于能读懂和能分析jQuery源码的阶段,不到自创一套类库的境界,
		 * 也许水平提高了jQuery不能满足我的时候,再回头看,兴许是另一重感悟了。
		 */
		ret = this.length > 1 && !guaranteedUnique[ name ] ? jQuery.unique( ret ) : ret; // 去重

		// rparentsprev = /^(?:parents|prevUntil|prevAll)/
		// 因为前边返回的结果是按照元素在文档中的位置顺序返回的,遇到rparentsprev则需要再反过来,方便使用
		if ( (this.length > 1 || rmultiselector.test( selector )) && rparentsprev.test( name ) ) {
			ret = ret.reverse(); // 倒序
		}

		return this.pushStack( ret, name, args.join(",") ); // 构造jQuery对象
	};
})

发表评论

邮箱地址不会被公开。 必填项已用*标注