【问题标题】:Using jQuery is there a way to find the farthest (deepest, or most nested) child element?使用 jQuery 有没有办法找到最远(最深或最嵌套)的子元素?
【发布时间】:2013-10-08 21:44:49
【问题描述】:

如果我有一组元素:

<div class="start">
    <div>
        <span>Text 1</span>
    </div>
</div>

<div class="start">
    <span>
        <div>
            <span>Text 2</span>
        </div>
        <div>
            <div>
                <span>Text 3</span>
            </div>
        </div>
    </span>
</div>

在事先不知道元素结构具体是什么的情况下,检索嵌套最多的子元素(在本例中为“Text 1”和“Text 3”)的最佳方法是什么?

这是我正在使用的jsFiddle

另外,如果之前有人问过这个问题,我深表歉意,但我找不到类似这个问题的具体问题。

【问题讨论】:

  • 做一个while循环,直到current.children的长度为0。但是,你如何处理有多个div的情况,例如单个跨度的多个div?那么您将拥有多个嵌套最多的子元素。
  • @DavidThomas 这是同一个问题,但这里提出的解决方案侧重于递归,而使用 while 循环。
  • 如果您的 HTML 没有最深节点作为唯一没有子节点和最后一个后代的节点,您将获得更完整的答案。 HTML 中的这两种简化都为您提供了不适用于一般情况的答案。在最深节点之后放置更多顶级子节点,如下所示:jsfiddle.net/jfriend00/8tC3a

标签: jquery


【解决方案1】:

这是一个使用我之前编写的 treeWalk 函数的实现,然后将其包装在一个 jquery 方法中,该方法在传入的 jQuery 对象中查找每个项目的最深后代,并返回一个包含这些节点的新 jQuery 对象。

递归和大量 jQuery 的解决方案可以用更少的代码完成,但它可能会更慢。这是基于遍历树的通用原生 JS 树遍历函数。

使用比 OP 的 HTML 更复杂的 HTML 测试用例的工作演示:http://jsfiddle.net/jfriend00/8tC3a/

$.fn.findDeepest = function() {
    var results = [];
    this.each(function() {
        var deepLevel = 0;
        var deepNode = this;
        treeWalkFast(this, function(node, level) {
            if (level > deepLevel) {
                deepLevel = level;
                deepNode = node;
            }
        });
        results.push(deepNode);
    });
    return this.pushStack(results);
};

var treeWalkFast = (function() {
    // create closure for constants
    var skipTags = {"SCRIPT": true, "IFRAME": true, "OBJECT": true, "EMBED": true};
    return function(parent, fn, allNodes) {
        var node = parent.firstChild, nextNode;
        var level = 1;
        while (node && node != parent) {
            if (allNodes || node.nodeType === 1) {
                if (fn(node, level) === false) {
                    return(false);
                }
            }
            // if it's an element &&
            //    has children &&
            //    has a tagname && is not in the skipTags list
            //  then, we can enumerate children
            if (node.nodeType === 1 && node.firstChild && !(node.tagName && skipTags[node.tagName])) {                
                node = node.firstChild;
                ++level;
            } else if (node.nextSibling) {
                node = node.nextSibling;
            } else {
                // no child and no nextsibling
                // find parent that has a nextSibling
                --level;
                while ((node = node.parentNode) != parent) {
                    if (node.nextSibling) {
                        node = node.nextSibling;
                        break;
                    }
                    --level;
                }
            }
        }
    }
})();

var deeps = $(".start").findDeepest();

deeps.each(function(i,v){
    $("#results").append(
        $("<li>").html($(v).prop("tagName") + " " + $(v).html())
    );
});

【讨论】:

  • 这正是我想要的,谢谢!我喜欢它的运行速度,尽管使用 jQuery 方法会使它更简单。
  • @MatthewKeefe - 是的,大多数时候,为了编程的方便和速度,jQuery 的额外开销是值得的。但是对于涉及大量迭代并且对速度敏感的任何事情,您通常希望至少从内部循环中删除新 jQuery 对象的创建。无论如何,很高兴这对你有用。
【解决方案2】:

您需要使用:last 过滤器

$('.start').find(':last');

工作小提琴:http://jsfiddle.net/BmEzd/1/

【讨论】:

  • 这只会找到最后一个后代,而不是最深的后代,因此如果最深的后代不是最后一个后代,它将不起作用。它仅适用于 OP 的 HTML 示例,因为它不涵盖一般情况。
【解决方案3】:

另一种方法(可能不那么快),代码行数最少,如下:

var all_matched_elements = $(":contains('" + text_to_search + "')");
var all_parent_elements = $(all_matched_elements).parents();
var all_deepest_matches = $(all_matched_elements).not(all_parent_elements);

如果您的文本位于一个元素内,该元素还具有其他子元素,则此方法特别有用,如下所示:

<div class="start">
    <div>
        <div>Text 1<span>Alpha Beta Gamma</span></div>
    </div>
</div>

但是,上面的代码不适用于如下所示的 DOM:

<div class="start">
        <div>
            <div id="zzz">search-text
                <div id="aaa">search-text</div>
            </div>  
        </div>
 </div>

在上述情况下,它只会选择 ID 为“#aaa”的最内层。它会删除 id "#zzz"。如果还想选择 id "#zzz",则必须修改开头的代码以从 "all_parent_elements" 中删除直接文本也与搜索文本匹配的元素。

【讨论】:

【解决方案4】:

我认为这对你有用。

var deepestLevel = 0;
var deepestLevelText = "";

function findDeepNested(element, currentLevel) {
    if ((element.children().length == 0) && (deepestLevel < currentLevel)) {
        // No children and current level is deeper than previous most nested level
        deepestLevelText="<li>" + element.text() + "</li>";
    }
    else { // there are children, keep diving
        element.children().each( function () {
            findDeepNested($(this), currentLevel + 1);
        });
    }
}

$(".start").each( function () {
    deepestLevel = 0;
    deepestLevelText = "";
    findDeepNested($(this), 0);
    $("#results").append(deepestLevelText);
});

Fiddle

【讨论】:

  • 这不会打印出所有的叶子节点,而不仅仅是最深的叶子节点吗?请记住,OP 的 HTML 示例不是在多个深度有多个分支的一般情况。
  • @jfriend00 这只会打印出没有子节点的文本。根据问题文本 1 和文本 2 必须包含在结果中,它们的深度不同,但它们是其结构内最深的节点。如果节点为空,则将添加一个空的 LI。但是您始终可以编辑我提供的 Fiddle 并使用您想要的案例对其进行测试。让我知道你的发现。
  • 我认为这不是 OP 所要求的。我认为他们想要传入的每个节点的单个最深节点,而不是所有叶节点。 OP 的 HTML 显示了一个过于简单的情况,而不是一般情况。看看这里的 HTML 测试用例:例如jsfiddle.net/jfriend00/8tC3a
  • 您可以将它们作为参数传递,然后返回它们。或者将 then 放在一个对象上并传递对该对象的引用。两者似乎都比使用全局变量更干净。或者,最干净的方法是创建一个顶级 shell 闭包,将它们定义为局部变量,然后递归调用局部函数而不是顶级函数。局部函数可以像全局变量一样引用闭包变量,但实际上并没有使用全局变量。我的答案在闭包中使用局部变量。
  • 感谢您的提示。我明天做。现在是凌晨 2 点 45 分... :-)
【解决方案5】:
<script type="text/javascript">
    function findFarthestNode(node)
    {
        if(node.parent().length && node.parent().prop("tagName") != "BODY")
            return findFarthestNode(node.parent());
        return node;
    }
    jQuery(document).ready(function() {
        node = jQuery("span");
        farthest = findFarthestNode(node);
        console.log(farthest);
    });
</script>

【讨论】:

  • 抱歉,这不起作用。它返回一组 4 个节点,包括 &lt;span&gt;Selected&lt;/span&gt; 节点。
  • 但是 OP 想要选择每个 .start 元素中嵌套最多的子元素。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-11-12
  • 1970-01-01
  • 2013-06-18
  • 2017-01-17
  • 1970-01-01
  • 2023-03-22
相关资源
最近更新 更多