获取某个节点的所有后代节点(也是叶子)
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("*");
除了已经存在且功能完善的答案之外,我发现值得一提的是,只需通过 firstChild
、nextSibling
导航即可取消递归和许多由此产生的函数调用和 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 次,否则——在这种情况下,你可能应该考虑尽可能避免这种情况,而不是简单地优化它。
我有一个 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("*");
除了已经存在且功能完善的答案之外,我发现值得一提的是,只需通过 firstChild
、nextSibling
导航即可取消递归和许多由此产生的函数调用和 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 次,否则——在这种情况下,你可能应该考虑尽可能避免这种情况,而不是简单地优化它。