【问题标题】:DOM traversal without exluded elements没有排除元素的 DOM 遍历
【发布时间】:2020-07-14 02:39:09
【问题描述】:

我正在创建一个简单的脚本,它遍历 DOM 并返回一个包含在 DOM 中找到的元素的树对象。递归遍历本身非常简单,但我想/需要跳过某些元素并包含其他元素。我该怎么做?

这是我的 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: [],
        }
      ]
    }
  ]
}

任何帮助将不胜感激!

【问题讨论】:

  • 使用后代选择器而不是子组合器。
  • @Teemu 将在每次迭代中返回所有降序元素。我需要直接后裔,但跳过每个没有 data-element 属性的元素。

标签: javascript html recursion dom traversal


【解决方案1】:

为什么不改用 node.querySelectorAll(':scope [data-element]') 呢?这将涵盖所有具有该属性的元素,而不仅仅是第一层子元素。

这是一个小提琴:https://jsfiddle.net/cp21ykng/2/ 还是我错过了什么?

【讨论】:

  • 这将在每次迭代中返回所有降序元素。如您所见,它会返回两次“also-me”。
  • @frietkot 抱歉,我错过了,感谢您的澄清
【解决方案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>

【讨论】:

  • 谢谢,但我不想(不能)为不需要的元素添加属性。
  • 我按照您的要求更新了答案。希望这会有所帮助
【解决方案3】:

使用Array.prototype.flatMap 可以大大降低转换的复杂性 -

  1. 如果节点没有data-element值,
  2. 不包括节点;仅包含其子项的结果。
  3. 否则(通过归纳)节点确实具有data-element 值。在输出中包含此 element 以及此节点的 children

上面编号的点对应下面的源 cmets -

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,因为不需要额外的文档查询。但是,一些明显的改进是 -

  1. 将树的形状定义为单独的函数,branch
  2. 为重复任务定义一个助手,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) ]

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-02-19
    • 1970-01-01
    • 1970-01-01
    • 2013-03-28
    相关资源
    最近更新 更多