【发布时间】:2019-05-13 00:39:44
【问题描述】:
如何遍历 HTML DOM 并在 javascript 中列出所有具有深度的节点。
例子:
<div>
<img src="foo.jpg">
<p>
<span>bar</span>
</p>
</div>
会导致
- div 0
- img 1
- p 1
- 跨度 2
【问题讨论】:
标签: javascript html dom
如何遍历 HTML DOM 并在 javascript 中列出所有具有深度的节点。
例子:
<div>
<img src="foo.jpg">
<p>
<span>bar</span>
</p>
</div>
会导致
【问题讨论】:
标签: javascript html dom
编写一个跟踪深度的递归函数:
function element_list(el,depth) {
console.log(el+' '+depth);
for(var i=0; i<el.children.length; i++) {
element_list(el.children[i],depth+1);
}
}
element_list(document,0);
正如CodeiSir 指出的那样,这也会列出文本节点,但我们可以通过testing the nodeType 将它们过滤掉。此代码的变体将根据需要允许/忽略其他节点类型。
function element_list(el,depth) {
if (el.nodeType === 3) return;
【讨论】:
el.children 将只返回元素,因此不会返回“文本节点”,尽管它会返回 SPAN 元素。如果您确实希望打印出文本节点,则必须输入el.childNodes。除此之外,这既简单又实用,尽管我希望不涉及递归的TreeWalker [developer.mozilla.org/en-US/docs/Web/API/Document/… 能够找出当前节点的深度。
请注意,其他答案是/不正确的地方......
这也会过滤掉“TEXT”节点,而不是输出BODY标签。
function getDef(element, def) {
var str = ""
var childs = element.childNodes
for (var i = 0; i < childs.length; ++i) {
if (childs[i].nodeType != 3) {
str += childs[i].nodeName + " " + def + "<br />"
str += getDef(childs[i], def + 1)
}
}
return str
}
// Example
document.body.innerHTML = getDef(document.body, 0)
<div>
<img src="foo.jpg">
<p>
<span>bar</span>
</p>
</div>
【讨论】:
是的,你可以!您必须进行迭代和一些逻辑来创建这棵树,但对于您的示例,您可以执行以下操作:
var tracker = {};
Array.from(document.querySelectorAll("*")).forEach(node => {
if (!tracker[node.tagName]) tracker[node.tagName] = 1;
else tracker[node.tagName]++;
});
console.log(tracker);
您可以修改它以在子节点的递归子集上运行。这只是迭代整个文档。
检查this fiddle 并打开控制台以查看tracker 的输出,其中计算并列出了标签名称。要添加深度,只需将parentNode.length 一直向上抓取即可。
这是一个更新的脚本,我认为可以进行深度计数;
var tracker = {};
var depth = 0;
var prevNode;
Array.from(document.querySelectorAll("*")).forEach(node => {
if (!tracker[node.tagName]) tracker[node.tagName] = 1;
else tracker[node.tagName]++;
console.log("Node depth:", node.tagName, depth);
if (node.parentNode != prevNode) depth++;
prevNode = node;
});
console.log(tracker);
【讨论】:
getElementDepth 返回节点的绝对深度(从html 节点开始),要获得两个节点之间的深度差,您只需从另一个节点减去绝对深度即可。
function getElementDepthRec(element,depth)
{
if(element.parentNode==null)
return depth;
else
return getElementDepthRec(element.parentNode,depth+1);
}
function getElementDepth(element)
{
return getElementDepthRec(element,0);
}
function clickEvent() {
alert(getElementDepth(document.getElementById("d1")));
}
<!DOCTYPE html>
<html>
<body>
<div>
<div id="d1">
</div>
</div>
<button onclick="clickEvent()">calculate depth</button>
</body>
</html>
【讨论】:
我最初的解决方案是使用while 循环将每个元素沿 DOM 向上遍历以确定其深度:
var el = document.querySelectorAll('body *'), //all element nodes, in document order
depth,
output= document.getElementById('output'),
obj;
for (var i = 0; i < el.length; i++) {
depth = 0;
obj = el[i];
while (obj.parentNode !== document.body) { //walk the DOM
depth++;
obj = obj.parentNode;
}
output.textContent+= depth + ' ' + el[i].tagName + '\n';
}
<div>
<img src="foo.jpg">
<p>
<span>bar</span>
</p>
</div>
<hr>
<pre id="output"></pre>
我想出了一个新的解决方案,它将每个元素的深度存储在一个对象中。由于querySelectorAll() 按文档顺序返回元素,因此父节点总是出现在子节点之前。所以子节点的深度可以计算为其父节点的深度加一。
这样,我们可以在没有递归的情况下一次性确定深度:
var el = document.querySelectorAll('body *'), //all element nodes, in document order
depths= { //stores the depths of each element
[document.body]: -1 //initialize the object
},
output= document.getElementById('output');
for (var i = 0; i < el.length; i++) {
depths[el[i]] = depths[el[i].parentNode] + 1;
output.textContent+= depths[el[i]] + ' ' + el[i].tagName + '\n';
}
<div>
<img src="foo.jpg">
<p>
<span>bar</span>
</p>
</div>
<hr>
<pre id="output"></pre>
【讨论】:
任何人都在寻找在不使用递归的情况下遍历节点下的树的东西*,但它也为您提供深度(相对于头节点)......以及任何时候的兄弟祖先坐标:
function walkDOM( headNode ){
const stack = [ headNode ];
const depthCountDowns = [ 1 ];
while (stack.length > 0) {
const node = stack.pop();
console.log( '\ndepth ' + ( depthCountDowns.length - 1 ) + ', node: ');
console.log( node );
let lastIndex = depthCountDowns.length - 1;
depthCountDowns[ lastIndex ] = depthCountDowns[ lastIndex ] - 1;
if( node.childNodes.length ){
depthCountDowns.push( node.childNodes.length );
stack.push( ... Array.from( node.childNodes ).reverse() );
}
while( depthCountDowns[ depthCountDowns.length - 1 ] === 0 ){
depthCountDowns.splice( -1 );
}
}
}
walkDOM( el );
PS 可以理解,我已经输入了> 0 和=== 0 以试图提高清晰度...首先可以省略,第二个当然可以替换为前导!。
* 看看 here 了解关于 JS 中递归成本的骇人听闻的真相(无论如何,2018-02-01 的当代实现!)
【讨论】:
您可以使用 Jquery 来做到这一点
$('#divId').children().each(function () {
// "this" is the current element
});
html 应该是这样的:
<div id="divId">
<img src="foo.jpg">
<p>
<span>bar</span>
</p>
【讨论】:
value(这些元素都没有,因为它们不是输入元素),而不是它们的文档节点深度。
(() => {
const el = document.querySelectorAll('body *');
const depths = new Map();
depths.set(document.body, -1)
el.forEach((e) => {
const p = e.parentNode;
const d = depths.get(p);
depths.set(e, d + 1);
})
return depths;
})()
这是Rick Hitchcocks 的答案,但使用地图而不是对象
Map(5) {body => -1, div => 0, img => 1, p => 1, span => 2} 中的结果
【讨论】: