如何在JavaScript中计算嵌套元素的数量

5
我有一个未知的、复杂的DOM结构,并且正在寻找具有给定类(.bar)的最大嵌套元素数量。
我需要一个(递归)方法,它返回嵌套元素的最大层数。在这个例子中,应该是3。
<div class="root">
    <div class="foo"></div>
    <div class="foo">
        <div class="bar"> <!-- #1 -->
            <div class="foo"></div>
            <div class="foo"></div>
        </div>
    </div>
    <div class="foo"></div>
    <div class="foo">
        <div class="bar"> <!-- #1 -->
            <div class="foo"></div>
            <div class="foo"></div>
            <div class="foo">
                <div class="bar"> <!-- #2 -->
                    <div class="foo"></div>
                    <div class="foo"></div>
                    <div class="foo">
                        <div class="bar"> <!-- #3 --> <!-- << this is the "most nested element" -->
                            <div class="foo"></div>
                            <div class="foo"></div>
                        </div>
                    </div>
                </div>
            </div>
        </div>
    </div>
    <div class="foo"></div>
    <div class="foo">
        <div class="bar"> <!-- #1 -->
            <div class="foo"></div>
            <div class="foo"></div>
            <div class="foo"></div>
        </div>
    </div>
    <div class="bar"> <!-- #1 -->
        <div class="foo"></div>
        <div class="foo"></div>
        <div class="foo">
            <div class="bar"> <!-- #2 -->
                <div class="foo"></div>
                <div class="foo"></div>
                <div class="foo"></div>
            </div>
        </div>
    </div>
</div>


所以我的方法是使用querySelectorAll('.bar'):
let d = 0;

function my_counter(root, depth = 0) {
    //get all items
    let items = root.querySelectorAll('.bar');

    d = Math.max(d, depth);

    //for each item check
    items.forEach((item) => {
        my_counter(item, depth + 1);
    });

    return d;
}

这种方法在我看来显得很繁琐且资源消耗大,所以我正在寻找更好的解决方案。而且,如果元素被移除,计数会发生变化,但Math.max并不考虑这一点。

1
这种方法在我看来显得不太干净,而且资源消耗较大,所以我正在寻找更好的解决方案。你有进行过基准测试吗?"资源消耗较大"是相对的。 - Marco
4个回答

2
这是一个递归函数,它通过指定的选择器来减少查询到的子元素。

const findMaxDepth = (root, selector, depth = 0) =>
  [...root.querySelectorAll(selector)].reduce((maxDepth, child) =>
    Math.max(maxDepth, findMaxDepth(child, selector, depth + 1)), depth);

const root = document.querySelector('.root');

console.log("Max depth of '.bar':", findMaxDepth(root, '.bar')); // 3
console.log("Max depth of '.foo':", findMaxDepth(root, '.foo')); // 4
<div class="root">
  <div class="foo"></div>
  <div class="foo">
    <div class="bar">
      <!-- #1 -->
      <div class="foo"></div>
      <div class="foo"></div>
    </div>
  </div>
  <div class="foo"></div>
  <div class="foo">
    <div class="bar">
      <!-- #1 -->
      <div class="foo"></div>
      <div class="foo"></div>
      <div class="foo">
        <div class="bar">
          <!-- #2 -->
          <div class="foo"></div>
          <div class="foo"></div>
          <div class="foo">
            <div class="bar">
              <!-- #3 -->
              <!-- << this is the "most nested element" -->
              <div class="foo"></div>
              <div class="foo"></div>
            </div>
          </div>
        </div>
      </div>
    </div>
  </div>
  <div class="foo"></div>
  <div class="foo">
    <div class="bar">
      <!-- #1 -->
      <div class="foo"></div>
      <div class="foo"></div>
      <div class="foo"></div>
    </div>
  </div>
  <div class="bar">
    <!-- #1 -->
    <div class="foo"></div>
    <div class="foo"></div>
    <div class="foo">
      <div class="bar">
        <!-- #2 -->
        <div class="foo"></div>
        <div class="foo"></div>
        <div class="foo"></div>
      </div>
    </div>
  </div>
</div>


1
你可以通过简单地添加CSS嵌套来完成这个,而无需迭代元素。

function findMaxDepth (className, deep = 0) {
  return document.querySelector(className)
       ? findMaxDepth(className + ' ' + className.split(' ')[0], ++deep)
       : deep;
}

console.log('.bar: ', findMaxDepth('.bar'));
console.log('.foo: ', findMaxDepth('.foo'));
<div class="root">
  <div class="foo"></div>
  <div class="foo">
    <div class="bar">
      <!-- #1 -->
      <div class="foo"></div>
      <div class="foo"></div>
    </div>
  </div>
  <div class="foo"></div>
  <div class="foo">
    <div class="bar">
      <!-- #1 -->
      <div class="foo"></div>
      <div class="foo"></div>
      <div class="foo">
        <div class="bar">
          <!-- #2 -->
          <div class="foo"></div>
          <div class="foo"></div>
          <div class="foo">
            <div class="bar">
              <!-- #3 -->
              <!-- << this is the "most nested element" -->
              <div class="foo"></div>
              <div class="foo"></div>
            </div>
          </div>
        </div>
      </div>
    </div>
  </div>
  <div class="foo"></div>
  <div class="foo">
    <div class="bar">
      <!-- #1 -->
      <div class="foo"></div>
      <div class="foo"></div>
      <div class="foo"></div>
    </div>
  </div>
  <div class="bar">
    <!-- #1 -->
    <div class="foo"></div>
    <div class="foo"></div>
    <div class="foo">
      <div class="bar">
        <!-- #2 -->
        <div class="foo"></div>
        <div class="foo"></div>
        <div class="foo"></div>
      </div>
    </div>
  </div>
</div>


0
你可以直接在递归函数中返回和更新深度值。此外,由于你只对.bar元素的最大深度感兴趣,所以不需要多次使用querySelectorAll('.bar')。以下是你代码的更简洁版本:

function findMaxNestedDepth(root, depth = 0) {
  let maxDepth = depth;

  // Get all .bar elements within the current root
  const barElements = root.querySelectorAll('.bar');

  // Iterate through each .bar element and recursively check their nested depth
  barElements.forEach((barElement) => {
    const nestedDepth = findMaxNestedDepth(barElement, depth + 1);
    maxDepth = Math.max(maxDepth, nestedDepth);
  });

  return maxDepth;
}

const rootElement = document.querySelector('.root');
const maxDepth = findMaxNestedDepth(rootElement);
console.log('Max Nested Depth:', maxDepth);
<div class="root">
  <div class="foo"></div>
  <div class="foo">
    <div class="bar">
      <!-- #1 -->
      <div class="foo"></div>
      <div class="foo"></div>
    </div>
  </div>
  <div class="foo"></div>
  <div class="foo">
    <div class="bar">
      <!-- #1 -->
      <div class="foo"></div>
      <div class="foo"></div>
      <div class="foo">
        <div class="bar">
          <!-- #2 -->
          <div class="foo"></div>
          <div class="foo"></div>
          <div class="foo">
            <div class="bar">
              <!-- #3 -->
              <!-- << this is the "most nested element" -->
              <div class="foo"></div>
              <div class="foo"></div>
            </div>
          </div>
        </div>
      </div>
    </div>
  </div>
  <div class="foo"></div>
  <div class="foo">
    <div class="bar">
      <!-- #1 -->
      <div class="foo"></div>
      <div class="foo"></div>
      <div class="foo"></div>
    </div>
  </div>
  <div class="bar">
    <!-- #1 -->
    <div class="foo"></div>
    <div class="foo"></div>
    <div class="foo">
      <div class="bar">
        <!-- #2 -->
        <div class="foo"></div>
        <div class="foo"></div>
        <div class="foo"></div>
      </div>
    </div>
  </div>
</div>


0
这是一个函数,通过构建子选择器来确定与选择器匹配的元素的最大深度。
function findMaxDepth(selector) {
  const find = (lastDepth) =>
    document.querySelector((selector + ' ').repeat(lastDepth + 1))
      ? find(lastDepth + 1)
      : lastDepth

  return find(0)
}

它支持任意选择器。如果没有匹配的元素,则返回0。

内部的find函数递归地查找更深层次的元素。 给定达到的深度,它构建一个更深层次的选择器,并检查是否有任何匹配的元素。 如果有,它使用新的深度调用自身并返回结果。 如果没有,它简单地返回传入的达到的深度。

构建的选择器中有一个尾随空格,但不影响任何内容。
您还可以使用数组来构建它,但效率会较低:

Array(depth + 1).fill(selector).join(" ")

这是一个展示它运行情况的片段:

function findMaxDepth(selector) {
  const find = (lastDepth) =>
    document.querySelector((selector + ' ').repeat(lastDepth + 1))
      ? find(lastDepth + 1)
      : lastDepth

  return find(0)
}

console.log("Max depth of '.foo':", findMaxDepth('.foo'))
console.log("Max depth of '.bar':", findMaxDepth('.bar'))
console.log("Max depth of '.baz':", findMaxDepth('.baz'))

console.log("---")

// You can use abritrary selectors.
// For example, the depth of groups of at least two siblings matching '.foo':
console.log("Max depth of '.foo + .foo':", findMaxDepth('.foo + .foo'))
// Or elements matching '.foo' that are the third child:
console.log("Max depth of '.foo:nth-child(3)':", findMaxDepth('.foo:nth-child(3)'))
<div class="root">
  <div class="foo"></div>
  <div class="foo">
    <!-- #1 -->
    <div class="bar">
      <div class="foo"></div>
      <div class="foo"></div>
    </div>
  </div>
  <div class="foo"></div>
  <div class="foo">
    <!-- #1 -->
    <div class="bar">
      <div class="foo"></div>
      <div class="foo"></div>
      <div class="foo">
        <!-- #2 -->
        <div class="bar">
          <div class="foo"></div>
          <div class="foo"></div>
          <div class="foo">
            <!-- #3 - This is the "most nested element" -->
            <div class="bar">
              <div class="foo"></div>
              <div class="foo"></div>
            </div>
          </div>
        </div>
      </div>
    </div>
  </div>
  <div class="foo"></div>
  <div class="foo">
    <!-- #1 -->
    <div class="bar">
      <div class="foo"></div>
      <div class="foo"></div>
      <div class="foo"></div>
    </div>
  </div>
  <!-- #1 -->
  <div class="bar">
    <div class="foo"></div>
    <div class="foo"></div>
    <div class="foo">
      <!-- #2 -->
      <div class="bar">
        <div class="foo"></div>
        <div class="foo"></div>
        <div class="foo"></div>
      </div>
    </div>
  </div>
</div>

基本思想(构建子选择器的思路)来自@imhvost的回答。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接