JavaScript中递归+||运算符导致for循环索引异常跳出问题
为什么我的JavaScript树形遍历for循环没有遍历所有子节点?
问题代码
<html> <head><head> <body> <script> class Test { constructor(name, children) { this.children = []; this.test = () => { let result = false; for (let i = 0; i < this.children.length; i++) { console.log(this.children[i].name); result = result || this.children[i].test(); } if (this.name.includes('2')) result = true; return result; }; this.name = name; this.children = children; } } const t = new Test('1', [ new Test('11', [ new Test('111', []), new Test('112', []), ]), new Test('12', [ new Test('121', []), new Test('122', []), ]), ]); const result = t.test(); console.log(result); </script> </body> </html>
问题现象
这段代码中,Test类的test()方法通过for循环遍历子节点并调用其test(),试图实现类似数组some()的逻辑(只要有一个子节点返回true,整体返回true)。但实际运行时,名称为121和122的实例的test()方法从未被调用。
同时观察到几个相关现象:
- 将
result = result || this.children[i].test();拆分为两行:const subResult = this.children[i].test(); result = result || subResult;,所有子节点都会被正常遍历。 - 把for循环条件改为
i <= this.children.length,会出现循环索引异常跳出的情况。 - 移除
if (this.name.includes('2')) result = true;后,所有子节点的test()都会被调用。 - 该现象在Chrome、Firefox、Safari中表现一致。
解答:符合规范,原因是逻辑或的短路求值
这个行为完全符合JavaScript规范,核心原因是逻辑或运算符(||)的短路求值特性:
逻辑或运算中,如果左边的操作数是真值(truthy),则直接返回左边的值,不会执行右边的表达式——这是JavaScript的标准行为。
具体执行流程拆解
- 根节点
t调用test(),初始result = false,开始遍历它的两个子节点(11和12)。 - 遍历第一个子节点11,调用
11.test():- 11的
test()初始result = false,遍历它的两个子节点(111和112)。 - 调用
111.test():111的name不含'2',返回false,11的result仍为false。 - 调用
112.test():112的name包含'2',result被设为true,返回true。此时11的result变为true,11.test()最终返回true。
- 11的
- 回到根节点的循环中,执行
result = false || 11.test()——因为右边返回true,根节点的result变为true。 - 循环索引
i递增到1,此时执行result = true || this.children[1].test():- 左边的
result已经是真值(true),触发短路求值,右边的this.children[1].test()(即12节点的test())完全不会被执行。 - 因此12节点及其子节点121、122的
test()都没有被调用,循环继续直到结束。
- 左边的
对应现象的解释
- 现象1:拆分代码后,不管
result当前是什么,都会先执行this.children[i].test()获取subResult,短路求值只影响result = result || subResult这一步,不会跳过子节点的方法调用,所以所有子节点都会被遍历。 - 现象2:当
i等于this.children.length时,this.children[i]是undefined,访问undefined.name会抛出TypeError,导致循环异常终止。 - 现象3:移除
if (this.name.includes('2')) result = true;后,所有test()都返回false,每次result = false || ...都会执行右边的表达式,不会触发短路,因此所有子节点都会被调用。
内容的提问来源于stack exchange,提问作者Hodol
相关产品推荐
相关产品推荐

