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