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 可以大大降低转换的复杂性 -

  1. 如果节点没有data-element值,
  2. 不包含该节点;仅包含其子项的结果。
  3. 否则(通过归纳)节点 确实 有一个 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,因为不需要额外的文档查询。但是,一些明显的改进是 -

  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) ]