获取某个节点的所有后代节点(也是叶子)

Get all the descendant nodes (also the leaves) of a certain node

我有一个 html 文档包含一个 <div id="main">。这个div里面可能有好几层节点,没有一个精确的结构,因为是创建文档内容的用户。 我想使用 JavaScript 函数 return 包含 div id="main" 中的所有节点。任何标签都是,考虑到可能有不同级别的children.

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

...

<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将return一个object个节点的数组(我不知道如何表示,所以我列出它们):

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

从例子中我们看到,你必须return所有个节点,甚至叶节点(即#text节点).

现在我有这个功能return除叶子之外的所有节点:

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

其实这个特性应用在上面的例子中returns:

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

没有#text 节点。 此外,如果文本元素 return 通过该方法以这种方式编辑:

all[elem].children.length

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

谢谢

递归到 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") );

children属性只有returns个元素节点。如果你想要全部 children,我建议使用 childNodes 属性。然后你可以循环遍历这个节点列表,并消除节点类型为 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)
        }
    }
}

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

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

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 。我将此作为 reader.

的练习

请记住,像这样遍历 DOM 很少会成为脚本中的瓶颈。除非你每秒要遍历一棵大 DOM 树许多 tens/hundreds 次,否则——在这种情况下,你可能应该考虑尽可能避免这种情况,而不是简单地优化它。