【问题标题】:Get all the descendant nodes (also the leaves) of a certain node获取某个节点的所有后代节点(也是叶子)
【发布时间】:2015-11-04 18:31:53
【问题描述】:

我有一个包含<div id="main"> 的html 文档。在这个div 内部可能有多个级别的节点,没有精确的结构,因为是创建文档内容的用户。 我想使用 JavaScript 函数返回div id="main" 中的所有节点。任何标签都是,考虑到可能有不同级别的孩子。

例如,如果我有这个文件:

...

<div id="main">

    <h1>bla bla</h1>

    <p>
        <b>fruits</b> apple<i>text</i>.
        <img src="..">image</img>
    </p>

    <div>
        <p></p>
        <p></p>
    </div>

    <p>..</p>

</div>
...

函数getNodes将返回一个对象节点数组(我不知道如何表示,所以我列出它们):

[h1, #text (= bla bla), p, b, #text (= fruits), #text (= _apple), i, #text (= text), img, #text (= image), div, p, p, p, #text (= ..)]

正如我们从示例中看到的,您必须返回所有节点,甚至是叶节点(即#text 节点)

现在我有这个函数可以返回除叶子之外的所有节点:

function getNodes() {
    var all = document.querySelectorAll("#main *");
    for (var elem = 0; elem < all.length; elem++) {
        //do something..
    }
}

其实上面例子中应用的这个特性返回:

[H1, P, B, I, IMG, DIV, P, P, P]

没有#text 节点。 另外,如果该方法以这种方式返回的文本元素:

all[elem].children.length

我知道(我在&lt;p&gt;fruits&lt;/p&gt; 上测试过)&lt;p&gt; 是一个 leaf 节点。 但是如果我构建 DOM 树,很明显它不是叶节点,在这个例子中叶节点是 #text...

谢谢

【问题讨论】:

    标签: javascript html tree


    【解决方案1】:

    递归到 DOM 的经典案例。

    function getDescendants(node, accum) {
        var i;
        accum = accum || [];
        for (i = 0; i < node.childNodes.length; i++) {
            accum.push(node.childNodes[i])
            getDescendants(node.childNodes[i], accum);
        }
        return accum;
    }
    

    getDescendants( document.querySelector("#main") );
    

    【讨论】:

    • 你算错了。该函数不能返回比那里更多的节点。您是否考虑过元素之间的纯空格文本节点?评论节点可能吗?修改函数以过滤掉那些不需要的节点很简单,我把它留给你。
    • 嗯,好吧,你怎么数?
    • 我根本不算。我所做的只是编写一个函数,该函数递归到 DOM 中并构建它找到的所有节点的数组,深度优先。如果您逐行查看代码,您会发现该函数不可能返回错误的结果。因此,您的期望必须偏离。
    • 我了解到您编写了一个可以正常工作的函数。但我想要一个返回 15 个节点的函数:[h1, #text (= bla bla), p, b, #text (= fruits), #text (= _apple), i, #text (= text), img, #text (= image), div, p, p, p, #text (= ..)]。你将返回 25 个节点:[text, h1, text, text, p, text, b, text, text, i, text, text, img, text, text, div, text, p, text, p, text, text, p, text, text].. 我只问为什么..
    • 太棒了。 :) 而且您也了解了 DOM 真正 的样子。这种知识在某些时候会很有用。
    【解决方案2】:

    children 属性只返回元素节点。如果您想要所有孩子,我建议使用 childNodes 属性。然后你可以遍历这个 nodeList,并消除 nodeType 为Node.ELEMENT_NODE 的节点,或者选择你感兴趣的其他节点类型

    所以试试类似的东西:

    var i, j, nodes
    var result=[] 
    var all = document.querySelectorAll("#main *");
    for (var elem = 0; elem < all.length; elem++) {
        result.push(all[elem].nodeName)
    
        nodes = all[elem].childNodes;
        for (i=0, j=nodes.length; i<j; i++) {
            if (nodes[i].nodeType == Node.TEXT_NODE) {
                result.push(nodes[i].nodeValue)
            }
        }
    }
    

    【讨论】:

    • 感谢您的回复。我认为elem[0].nodeName 有错误,所以我改为all[0].nodeName,结果是["H1", undefined, "H1", undefined, undefined, undefined, undefined, "H1", undefined, "H1", undefined, "H1", "H1", undefined, undefined, undefined, "H1", "H1", "H1", undefined].. 不起作用:(
    • 我已经更新了,我写了代码,找到了错误,修复了它,然后粘贴了原来的损坏代码 - 哎呀
    • 谢谢,现在可以了。只有一个问题。该数组还包含字符,所以元素的数量是20而不是15。我怎样才能确保删除这些字符?我认为问题出在\n\t(其他?)。谢谢
    【解决方案3】:

    如果你只需要html标签而不需要#text,你可以简单地使用这个:&lt;elem&gt;.querySelectorAll("*");

    【讨论】:

      【解决方案4】:

      除了已经存在且功能完善的答案之外,我发现值得一提的是,只需通过 firstChildnextSiblingparentNode 属性导航即可消除递归和许多由此产生的函数调用:

      function getDescendants(node) {
          var list = [], desc = node, checked = false, i = 0;
          do {
              checked || (list[i++] = desc);
              desc =
                  (!checked && desc.firstChild) ||
                  (checked = false, desc.nextSibling) ||
                  (checked = true, desc.parentNode);
          } while (desc !== node);
          return list;
      }
      

      (当我们遇到一个新节点时,我们将它添加到列表中,然后尝试转到它的第一个子节点。如果不存在,则获取下一个兄弟节点。每当找不到子节点或后续兄弟节点时,我们返回父级,同时设置checked 标志以避免再次将其添加到列表或重新进入其后代树。)

      这几乎在所有情况下都会大大提高性能。并不是说这里没有什么可以优化的,例如可以缓存我们进一步下降到层次结构中的节点,以便稍后在回来时摆脱parentNode。我把实现这个作为一个练习留给读者。

      请记住,像这样遍历 DOM 很少会成为脚本的瓶颈。除非您每秒通过数十/数百次大型 DOM 树,否则您可能应该考虑尽可能避免这种情况,而不是简单地对其进行优化。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2014-03-07
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2018-08-12
        • 1970-01-01
        相关资源
        最近更新 更多