显式声明let对递归场景下for循环变量的影响探究
为什么递归中的for循环用let声明变量才正常?
嘿,这个问题戳中了JS变量作用域的一个经典坑——本质是**let的块级作用域 vs 隐式声明(等价于var或全局变量)的函数/全局作用域**在递归场景下的差异,我给你掰扯清楚:
先搞懂两种声明方式的核心区别
你说的“移除let改为隐式声明i”,其实相当于把i变成了函数级(或全局)共享变量(非严格模式下未声明的变量会自动挂载到全局,严格模式下会直接报错);而用let声明时,i是块级作用域变量——每次for循环迭代都会创建一个独立的i实例,只属于当前这轮循环的代码块。
递归场景下的具体影响
1. 用let时:循环变量完全独立
每次循环迭代的i都是全新的、互不干扰的变量。比如外层递归的循环走到i=0,触发内层递归后,内层循环的i是自己的一套(从0开始递增),完全不会影响外层的i。等内层递归执行完回到外层,外层的i还是停留在0,接着会正常走到i=1、i=2……整个遍历逻辑完全符合预期。
2. 隐式声明(或var)时:变量被递归共享篡改
这时候整个函数(甚至全局)只有一个i实例。举个实际的坑点例子:
- 外层循环
i=0,遇到子数组触发递归 - 内层递归的for循环会把这个共享的
i从0一直加到子数组的长度 - 当递归返回外层时,
i已经变成了内层数组的长度,直接不满足外层循环的i < arr.length条件,循环提前终止 - 结果就是外层数组后面的元素完全没被遍历,统计的匹配数量自然不对,甚至可能出现死循环
举个简化代码对比
正常工作的版本(let声明):
function countMatches(arr, target) { let count = 0; for (let i = 0; i < arr.length; i++) { if (typeof arr[i] === 'string' && arr[i] === target) { count++; } else if (Array.isArray(arr[i])) { count += countMatches(arr[i], target); } } return count; }
异常的版本(隐式声明):
function countMatches(arr, target) { let count = 0; // 等价于var i,整个函数共享一个i for (i = 0; i < arr.length; i++) { if (typeof arr[i] === 'string' && arr[i] === target) { count++; } else if (Array.isArray(arr[i])) { count += countMatches(arr[i], target); } } return count; }
总结
let的块级作用域给每一轮循环都生成了独立的变量副本,确保递归的每一层循环都有自己的i,不会互相干扰;而隐式声明的变量是全局/函数级共享的,递归时会被内层循环意外修改,直接破坏外层循环的执行逻辑。
内容的提问来源于stack exchange,提问作者CodeAt30
相关产品推荐
相关产品推荐

