什么阻止函数被无限调用?

What is preventing function from being called infinitely?

函数目的是遍历[=​​23=]在每个子元素上通过回调函数传递。如果 traverseDom 调用自身并重新启动整个函数,我希望永远不会达到 element = element.nextElementSibling 。尽管我们最终会找到节点树中的最后一个子节点,但我认为即使在我们到达最后一个子节点之后,也没有什么能阻止该函数无限地调用自身以尝试寻找其他子节点。

function traverseDom(element, callback) {
  callback(element);
  element = element.firstElementChild;

  while (element) {
    traverseDom(element, callback);
    element = element.nextElementSibling;
  }
}
const subTree = document.getElementById("subTree");
traverseDom(subTree, function(element) {
  console.assert(element !== null, element.nodeName);
});
<div id="subTree">
  <form>
    <input type="text" />
  </form>
  <p>Paragraph</p>
  <span>Span</span>
</div>

我预计这会 运行 无限并且永远不会到达同级元素声明。

在最深的嵌套元素中,element.firstElementChild 将是 null,因此在将其分配给 element 后,以下条件将不成立:

while(element) {     

...所以根本没有进入循环。此时没有进一步的递归,函数 returns,并且可以发生回溯。在递归的前一层执行的函数可能还会进一步循环,但最终总会有最深的一层没有执行循环。这些代表 depth-first 遍历树中的叶子。

变量作用域

还有另一个方面可能会导致混淆:变量element本地当前函数执行上下文:它的值发生变化(由于一个赋值),不会影响调用函数中的同名变量。

为了澄清这一点,您还可以重写代码以使用您将分配给 child 节点的不同变量名称:

function traverseDom(element, callback) {
  callback(element);
  var child = element.firstElementChild;

  while (child) {
    traverseDom(child, callback);
    child = child.nextElementSibling;
  }
}
const subTree = document.getElementById("subTree");
traverseDom(subTree, function(element) {
  console.assert(element !== null, element.nodeName);
});
<div id="subTree">
  <form>
    <input type="text" />
  </form>
  <p>Paragraph</p>
  <span>Span</span>
</div>

此代码将产生相同的结果;它使用相同的逻辑,只是它没有为 element 分配新值,而是为该新值使用不同的变量(第一个 child)。但是注意当函数被递归调用时,child的值变成了parameter-variableelement.

的值

生成器使您能够以更方便的方式执行此操作 -

function* traverseDom (elem) {
  yield elem
  for (const child of elem.children)
    yield* traverseDom(child)
}

const subTree =
  document.getElementById("subTree")

for (const elem of traverseDom(subTree))
  console.log(elem, elem.nodeName)
  
for (const elem of traverseDom(subTree))
  console.assert(elem !== null, elem.nodeName)
<div id="subTree">
  <form>
    <input type="text" />
  </form>
  <p>Paragraph</p>
  <span>Span</span>
</div>

如果你更喜欢高阶函数,那也是可以的-

function traverseDom (f, elem) {
  f(elem)
  for (const child of elem.children)
    traverseDom(f, child)
}

const subTree =
  document.getElementById("subTree")

traverseDom
  ( elem => console.log(elem, elem.nodeName)
  , subTree
  )
  
traverseDom
  ( elem => console.assert(elem !== null, elem.nodeName)
  , subTree
  )
<div id="subTree">
  <form>
    <input type="text" />
  </form>
  <p>Paragraph</p>
  <span>Span</span>
</div>