подсчет узлов и элементов в дереве DOM с помощью рекурсивной функции JS - PullRequest
0 голосов
/ 14 октября 2019

Я хочу подсчитать все узлы и элементы (в двух разных переменных) дерева DOM, используя рекурсивную ванильную функцию JavaScript

я пробовал что-то вроде этого

    let nodeSum = 0;

function recursiveCount(node) {
  if (node.childNodes) {
    let childnodes = node.childNodes;
    nodeSum += childnodes.length;
    let nextchild = node.firstElementChild;
    let nextsibling = node.nextElementSibling;
    if (nextsibling) {
      return recursiveCount(nextsibling);
    }
    return recursiveCount(nextchild);
  }
  return;
}
recursiveCount(document);

этоerror TypeError: Не удается прочитать свойство 'childNodes' со значением NULL, почему это происходит?

Ответы [ 2 ]

0 голосов
/ 14 октября 2019

Вот способ подсчета узлов с помощью простой взаимной рекурсии. Это считает все узлы, включая текстовые узлы -

const count = ({ childNodes = [] }) =>
  1 + countChildren([...childNodes])
    
const countChildren = (nodes = []) =>
  nodes.reduce((r, n) => r + count(n), 0)

const e =
  document.querySelector('article')

console.log(count(e))
// 23
<article>
  <h1>Lorem Ipsum...</h1>
  <p>foo bar qux</p>
  <ul>
    <li>a</li>
    <li>b</li>
    <li>c</li>
  </ul>
  <p>foo bar qux</p>
</article>

Как видите, логика обхода узла запутана с логикой подсчета. Разделив обход в его собственную функцию, мы можем легко выполнять различные вычисления, не дублируя логику обхода.

Ниже count зависит от traverse, сохраняя свой уникальный фокус на конкретной (и, возможно, сложной) логике подсчета-

const traverse = function* (node = {})
{ yield node                           // include this node
  for (const child of node.childNodes) // and for each of this node's children
    yield* traverse(child)             // traverse each child
}

const count = (node = {}) =>
  Array                               // create array
    .from(traverse(node))             // of all nodes
    .reduce                           // then reduce
      ( ([ nodes, elems ], { nodeType }) => // each node
          nodeType === 1                    // if node is Element type,
            ? [ nodes + 1, elems + 1 ]      // count as node and elem
            : [ nodes + 1, elems ]          // otherwise just count as node
      , [ 0, 0 ]                      // using these initial counts
      )

const e =
  document.querySelector('article')

const [ nodes, elems ] =
  count(e)

console.log(`nodes: ${nodes}, elems: ${elems}`)
// nodes: 23, elems: 8
<article>
  <h1>Lorem Ipsum...</h1>
  <p>foo bar qux</p>
  <ul>
    <li>a</li>
    <li>b</li>
    <li>c</li>
  </ul>
  <p>foo bar qux</p>
</article>

Если цель состоит только в подсчете Element узлов, мы можем упростить reduce -

const traverse = function* (node = {})
{ yield node                           // include this node
  for (const child of node.childNodes) // and for each of this node's children
    yield* traverse(child)             // traverse each child
}

const count = (node = {}) =>
  Array                      // create array
    .from(traverse(node))    // of all nodes
    .reduce                  // then reduce
      ( (r, { nodeType }) => // each node
          nodeType === 1     // if node is Element type,
            ? r + 1          // increase count by one
            : r              // otherwise keep count the same
      , 0                    // using this initial count
      )

const e =
  document.querySelector('article')

const elems =
  count(e)

console.log(`elems: ${elems}`)
// elems: 8
<article>
  <h1>Lorem Ipsum...</h1>
  <p>foo bar qux</p>
  <ul>
    <li>a</li>
    <li>b</li>
    <li>c</li>
  </ul>
  <p>foo bar qux</p>
</article>

Что, вероятно, можно улучшить с помощью filter -

const add = (a = 0, b = 0) =>
  a + b

const count = (node = {}) =>
  Array                                       // create array
    .from(traverse(node))                     // of all nodes
    .filter(({ nodeType }) => nodeType === 1) // keeping only Element nodes
    .reduce(add, 0)                           // then sum
0 голосов
/ 14 октября 2019

Может возникнуть ошибка, когда node имеет некоторое значение childNodes, но ни один из дочерних элементов не является element. В этом случае nextchild = node.firstElementChild будет null, и вы передадите его здесь recursiveCount(nextchild) без каких-либо проверок, вызывающих бросок в этой строке: if (node.childNodes) {.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...