DOM遍历无排除元素
DOM traversal without exluded elements
我正在创建一个简单的脚本,它遍历 DOM 和 returns 一个树对象,其中包含在 DOM 中找到的元素。递归遍历本身很简单,但我 want/need 跳过某些元素并包含其他元素。我该怎么做?
这是我的 HTML:
<div data-element="from-here">
<div>skip me</div>
<div>
<div data-element="awesome">awesome text</div>
<div data-element="collect-me">
awesome text
<div data-element="also-me">
other text
<div class="but-not-me">...</div>
</div>
</div>
</div>
</div>
还有我的递归遍历代码:
const root = document.querySelector('[data-element="from-here"]');
function traverse(node) {
return {
element: node.dataset.element,
children: Array.from(node.querySelectorAll(':scope > div')).map(childNode => traverse(childNode)),
};
}
traverse(root);
如您所见,代码查询所有 div 元素,但我只需要具有 data-element
属性的元素。我不能只做 `node.querySelectorAll(':scope > [data-element]') 因为那不会超过第一个 div。
这是我想要的结果:
{
element: 'from-here',
children: [
{
element: 'awesome',
children: [],
},
{
element: 'collect-me',
children: [
{
element: 'also-me',
children: [],
}
]
}
]
}
如有任何帮助,我们将不胜感激!
为什么不改用 using node.querySelectorAll(':scope [data-element]')
?这将覆盖所有具有该属性的元素,而不仅仅是第一层子元素。
这是 fiddle:https://jsfiddle.net/cp21ykng/2/ 还是我遗漏了什么?
您可以使用Array.filter()
过滤掉没有data-element
属性的元素。
但是 为了包含包装器 div,您可能还需要添加 data-element
属性,如下所示。
const root = document.querySelector('[data-element="from-here"]');
function traverse(node) {
if (node.dataset.element) {
return {
element: node.dataset.element,
children: Array.from(node.querySelectorAll(':scope > div'))
.filter(child => {
return child.dataset.element || Array.from(child.children).some(grandChild => grandChild.dataset.element)
// If element has dataset or The child has some children with dataset attribute
})
.map(childNode => traverse(childNode)),
};
} else if (Array.from(node.children).some(child => child.dataset.element)) {
return Array.from(node.querySelectorAll(':scope > div')).filter(child => child.dataset.element).map(childNode => traverse(childNode))
}
}
console.log(traverse(root));
<div data-element="from-here">
<div>skip me</div>
<div>
<div data-element="awesome">awesome text</div>
<div data-element="collect-me">
awesome text
<div data-element="also-me">
other text
<div class="but-not-me">...</div>
</div>
</div>
</div>
</div>
使用 Array.prototype.flatMap
可以大大降低转换的复杂性 -
- 如果节点没有
data-element
值,
- 不包含该节点;仅包含其子项的结果。
- 否则(通过归纳)节点 确实 有一个
data-element
值。在输出中包括此 element
以及此节点的 children
.
上面的数字点对应下面的来源评论-
const root = document.querySelector('[data-element="from-here"]');
const toTree = ({ dataset = {}, children = [] }) =>
dataset.element === undefined // 1
? Array.from(children).flatMap(toTree) // 2
: [ { element: dataset.element // 3
, children: Array.from(children).flatMap(toTree)
}
]
console.log(toTree(root)[0])
<div data-element="from-here">
<div>skip me</div>
<div>
<div data-element="awesome">awesome text</div>
<div data-element="collect-me">
awesome text
<div data-element="also-me">
other text
<div class="but-not-me">...</div>
</div>
</div>
</div>
</div>
以上,请注意我们没有使用 querySelectorAll
,因为不需要额外的文档查询。但是,一些明显的改进是 -
- 将树的形状定义为单独的函数,
branch
- 为重复任务定义一个助手,
allToTree
const branch = (element = "", children = []) => // 1
({ element, children })
const allToTree = (nodes = []) => // 2
Array.from(nodes).flatMap(toTree)
toTree
现在没有了复杂性。我们的意图很明确,每个功能都易于编写、测试和维护 -
const toTree = ({ dataset = {}, children = [] }) =>
dataset.element === undefined
? allToTree(children)
: [ branch(dataset.element, allToTree(children) ]
我正在创建一个简单的脚本,它遍历 DOM 和 returns 一个树对象,其中包含在 DOM 中找到的元素。递归遍历本身很简单,但我 want/need 跳过某些元素并包含其他元素。我该怎么做?
这是我的 HTML:
<div data-element="from-here">
<div>skip me</div>
<div>
<div data-element="awesome">awesome text</div>
<div data-element="collect-me">
awesome text
<div data-element="also-me">
other text
<div class="but-not-me">...</div>
</div>
</div>
</div>
</div>
还有我的递归遍历代码:
const root = document.querySelector('[data-element="from-here"]');
function traverse(node) {
return {
element: node.dataset.element,
children: Array.from(node.querySelectorAll(':scope > div')).map(childNode => traverse(childNode)),
};
}
traverse(root);
如您所见,代码查询所有 div 元素,但我只需要具有 data-element
属性的元素。我不能只做 `node.querySelectorAll(':scope > [data-element]') 因为那不会超过第一个 div。
这是我想要的结果:
{
element: 'from-here',
children: [
{
element: 'awesome',
children: [],
},
{
element: 'collect-me',
children: [
{
element: 'also-me',
children: [],
}
]
}
]
}
如有任何帮助,我们将不胜感激!
为什么不改用 using node.querySelectorAll(':scope [data-element]')
?这将覆盖所有具有该属性的元素,而不仅仅是第一层子元素。
这是 fiddle:https://jsfiddle.net/cp21ykng/2/ 还是我遗漏了什么?
您可以使用Array.filter()
过滤掉没有data-element
属性的元素。
但是 为了包含包装器 div,您可能还需要添加 data-element
属性,如下所示。
const root = document.querySelector('[data-element="from-here"]');
function traverse(node) {
if (node.dataset.element) {
return {
element: node.dataset.element,
children: Array.from(node.querySelectorAll(':scope > div'))
.filter(child => {
return child.dataset.element || Array.from(child.children).some(grandChild => grandChild.dataset.element)
// If element has dataset or The child has some children with dataset attribute
})
.map(childNode => traverse(childNode)),
};
} else if (Array.from(node.children).some(child => child.dataset.element)) {
return Array.from(node.querySelectorAll(':scope > div')).filter(child => child.dataset.element).map(childNode => traverse(childNode))
}
}
console.log(traverse(root));
<div data-element="from-here">
<div>skip me</div>
<div>
<div data-element="awesome">awesome text</div>
<div data-element="collect-me">
awesome text
<div data-element="also-me">
other text
<div class="but-not-me">...</div>
</div>
</div>
</div>
</div>
使用 Array.prototype.flatMap
可以大大降低转换的复杂性 -
- 如果节点没有
data-element
值, - 不包含该节点;仅包含其子项的结果。
- 否则(通过归纳)节点 确实 有一个
data-element
值。在输出中包括此element
以及此节点的children
.
上面的数字点对应下面的来源评论-
const root = document.querySelector('[data-element="from-here"]');
const toTree = ({ dataset = {}, children = [] }) =>
dataset.element === undefined // 1
? Array.from(children).flatMap(toTree) // 2
: [ { element: dataset.element // 3
, children: Array.from(children).flatMap(toTree)
}
]
console.log(toTree(root)[0])
<div data-element="from-here">
<div>skip me</div>
<div>
<div data-element="awesome">awesome text</div>
<div data-element="collect-me">
awesome text
<div data-element="also-me">
other text
<div class="but-not-me">...</div>
</div>
</div>
</div>
</div>
以上,请注意我们没有使用 querySelectorAll
,因为不需要额外的文档查询。但是,一些明显的改进是 -
- 将树的形状定义为单独的函数,
branch
- 为重复任务定义一个助手,
allToTree
const branch = (element = "", children = []) => // 1
({ element, children })
const allToTree = (nodes = []) => // 2
Array.from(nodes).flatMap(toTree)
toTree
现在没有了复杂性。我们的意图很明确,每个功能都易于编写、测试和维护 -
const toTree = ({ dataset = {}, children = [] }) =>
dataset.element === undefined
? allToTree(children)
: [ branch(dataset.element, allToTree(children) ]