【转载】jQuery源码分析-11 DOM遍历-Traversing-DOM遍历方法
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对象 }; }) |