什么阻止函数被无限调用?
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>
函数目的是遍历[=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>