jQuery源码分析(十四): 节点遍历

前端基础 2016-11-28

起步

遍历简单的讲就是对数据中的每一个元素都查询一遍,不同的数据结构有不同的遍历方式,如数据通过索引遍历,链表通过指针遍历。遍历的方法也有所不同,如树的遍历分为前序遍历,中序遍历,后序遍历。

jq中的遍历包括了过滤,查找和串联元素。来自w3school的解释:

jQuery 遍历,意为“移动”,用于根据其相对于其他元素的关系来“查找”(或选取)HTML 元素。以某项选择开始,并沿着这个选择移动,直到抵达您期望的元素为止。

jq中关于dom的遍历包括遍历祖先,遍历同胞,遍历后代,过滤:

$('li').filter(':even').css('background-color', 'blue');
$('ul.level-2').children().css('background-color', 'yellow');
$('li.item-ii').find('li').css('background-color', 'blue');
$('p').parents();

遍历祖先

祖先是父、祖父或曾祖父等等。向上遍历 DOM 树,这些 jQuery 方法很有用,它们用于向上遍历 DOM 树:parent(), parents(), parentsUntil()

  • .parent() :在DOM树中搜索到这些元素的父级元素,从有序的向上匹配元素,并根据匹配的元素创建一个新的 jQuery 对象。
  • .parents():和.parent()方法是相似的,但后者只是进行了一个单级的DOM树查找
  • .parentsUntil() 方法会找遍所有这些元素的前辈元素,直到遇到了跟参数匹配的元素才会停止。返回的jQuery对象中包含了所有找到的前辈元素,除了与 .parentsUntil() 选择器匹配的那个元素。
    parent: function( elem ) {
    var parent = elem.parentNode;
    return parent && parent.nodeType !== 11 ? parent : null;
    },
    parents: function( elem ) {
    return jQuery.dir( elem, "parentNode" );
    },
    parentsUntil: function( elem, i, until ) {
    return jQuery.dir( elem, "parentNode", until );
    },

遍历同胞

同胞就是拥有相同的父元素。

  • .nextAll() 获得匹配元素集合中每个元素之后的所有同辈元素,由选择器进行筛选(可选)。
  • .nextUntil() 获得每个元素之后所有的同辈元素,直到遇到匹配选择器的元素为止。
  • .prevAll() 获得匹配元素集合中每个元素之前的所有同辈元素,由选择器进行筛选(可选)。
  • .prevUntil() 获得每个元素之前所有的同辈元素,直到遇到匹配选择器的元素为止。
  • .next() 获得匹配元素集合中每个元素的下一个同辈元素。
  • .prev() 获得匹配元素集合中每个元素紧邻的前一个同辈元素,由选择器筛选(可选)。
  • .siblings() 获得匹配元素集合中所有元素的同辈元素,由选择器筛选(可选)。

遍历后代

后代是子、孙、曾孙等等,通过 jQuery,您能够向下遍历 DOM 树,以查找元素的后代。

children()获得匹配元素集合中每个元素的子元素(仅儿子辈),选择器选择性筛选。因为就jQuery可以是一个DOM的合集对象,所以children就需要遍历每一个合集中的直接子元素了,并且最后需要构建一个新的jQuery对象。

.find()方法返回被选元素的后代元素,一路向下直到最后一个后代。还可以接受一个选择器表达式,匹配的元素将构造一个新的jQuery对象。.find().children()方法是相似的,但后者只是再DOM树中向下遍历一个层级。

function find(elem, selector) {
    return elem.querySelectorAll(selector);
}

遍历的结构设计

遍历的几口很多都具有相似的功能。

jQuery.extend({
    dir: function( elem, dir, until ) {
        var matched = [],
            truncate = until !== undefined;

        while ( (elem = elem[ dir ]) && elem.nodeType !== 9 ) {
            if ( elem.nodeType === 1 ) {
                if ( truncate && jQuery( elem ).is( until ) ) {
                    break;
                }
                matched.push( elem );
            }
        }
        return matched;
    },

    sibling: function( n, elem ) {
        var matched = [];

        for ( ; n; n = n.nextSibling ) {
            if ( n.nodeType === 1 && n !== elem ) {
                matched.push( n );
            }
        }

        return matched;
    }
});

nodeType是以数字的形式表示dom节点的节点类型,如果是元素节点,则 nodeType 属性将返回 1,如果是属性节点,则 nodeType 属性将返回 2。dir = function( elem, dir, until )函数的作用就是返回所有满足dom的[ dir ]属性的对象。

20161128164341.png

function sibling( cur, dir ) {
    while ( (cur = cur[dir]) && cur.nodeType !== 1 ) {}
    return cur;
}

此函数与$.subling()的差别只是sibling返回的是第一个兄弟节点。

在jQuery内部的运用最多的就是接口的抽象合并,相同功能的代码功能合并处理:

jQuery.each({
    parent: function( elem ) {
        var parent = elem.parentNode;
        return parent && parent.nodeType !== 11 ? parent : null;
    },
    parents: function( elem ) {
        return jQuery.dir( elem, "parentNode" );
    },
    parentsUntil: function( elem, i, until ) {
        return jQuery.dir( elem, "parentNode", until );
    },
    next: function( elem ) {
        return sibling( elem, "nextSibling" );
    },
    prev: function( elem ) {
        return sibling( elem, "previousSibling" );
    },
    nextAll: function( elem ) {
        return jQuery.dir( elem, "nextSibling" );
    },
    prevAll: function( elem ) {
        return jQuery.dir( elem, "previousSibling" );
    },
    nextUntil: function( elem, i, until ) {
        return jQuery.dir( elem, "nextSibling", until );
    },
    prevUntil: function( elem, i, until ) {
        return jQuery.dir( elem, "previousSibling", until );
    },
    siblings: function( elem ) {
        return jQuery.sibling( ( elem.parentNode || {} ).firstChild, elem );
    },
    children: function( elem ) {
        return jQuery.sibling( elem.firstChild );
    },
    contents: function( elem ) {
        return elem.contentDocument || jQuery.merge( [], elem.childNodes );
    }
}, function( name, fn ) {
    jQuery.fn[ name ] = function( until, selector ) {
        var matched = jQuery.map( this, fn, until );

        if ( name.slice( -5 ) !== "Until" ) {
            selector = until;
        }

        if ( selector && typeof selector === "string" ) {
            matched = jQuery.filter( selector, matched );
        }

        if ( this.length > 1 ) {
            // Remove duplicates
            if ( !guaranteedUnique[ name ] ) {
                jQuery.unique( matched );
            }

            // Reverse order for parents* and prev-derivatives
            if ( rparentsprev.test( name ) ) {
                matched.reverse();
            }
        }

        return this.pushStack( matched );
    };
});

可以看出,结合each迭代,节约的大量的代码空间。parent,parents,parentsUntil三个函数,把它们并成dir函数。

过滤的结果会将他们包装为集合,用一个jq对象封装。然后将jq对象放入维护栈中this.pushStack( matched )


本文由 hongweipeng 创作,采用 知识共享署名 3.0,可自由转载、引用,但需署名作者且注明文章出处。

如果对您有用,您的支持将鼓励我继续创作!