JavaScript for循环迭代变量用let声明与否的差异解析
问题现象
在Codewars平台完成行列式计算挑战时遇到稳定复现的异常:
- 未给for循环迭代变量
i添加let声明的版本,处理3×3矩阵时运行结果正常,但处理4×4及更大规模矩阵时,最外层for循环仅会执行1次,计算结果完全错误 - 给循环变量
i加上let关键字声明后,所有3×3及以上规模的矩阵均可正常完成行列式计算
核心原因
两种写法的本质区别是变量的作用域规则不同:
- 不使用
let/const/var直接赋值的变量,会被挂载到全局作用域成为全局变量,所有递归调用产生的函数执行实例,都会读写同一个i - 使用
let声明的变量属于块级作用域,每一层函数调用、每一轮循环的i都是独立的,内层逻辑修改i不会影响外层的循环计数
这个bug只在4×4及以上矩阵触发、3×3矩阵正常完全是巧合:
行列式计算采用递归展开的逻辑,最外层for循环第一轮执行(初始i=0)时,就会调用自身计算去掉第一行第i列后的子矩阵行列式。
- 处理3×3矩阵时,递归深度只有2层,内层递归对全局
i的修改,在递归返回外层时刚好还能满足循环的判断条件,循环可以走完剩余迭代,表面上看运行正常,实际上变量污染已经存在,只是结果刚好正确。 - 处理4×4及更大矩阵时,递归层级更深,多层嵌套的递归调用里的for循环,会反复修改全局共享的
i。等最外层第一轮循环的递归调用全部执行完、回到外层循环的判断逻辑时,全局i已经被累加到大于等于矩阵边长的值,i < matrix[0].length的条件直接不成立,循环立刻终止,看起来就像只执行了一次。
验证示例
可以用极简的递归函数复现这个问题:
function testRecursion(loopLen) { for(i = 0; i < loopLen; i++) { console.log('进入循环时i值:', i) if(loopLen > 1) { testRecursion(loopLen - 1) } console.log('内层递归执行完后i值:', i) } } testRecursion(4)
运行后可以看到,第一次进入循环时i=0,等内层递归全部跑完,i的值已经被修改为3,下一轮循环判断直接不满足,整个循环直接结束。如果把for循环改成for(let i = 0; i < loopLen; i++),每一层的i互不干扰,循环会按预期跑完所有轮次。
额外提示:给出的代码里,
j/k/sum/subMat/subMat2这些变量也都没有做声明,同样存在全局变量污染的问题,只是这次没有触发明显bug。在开启JS严格模式时,未声明就赋值的变量会直接抛出错误,建议所有变量都显式声明作用域,避免这类难以排查的递归/循环bug。
代码对比
存在bug的版本
//Buggy code const determinant = (matrix) => { if(matrix[0].length === 1) return matrix[0][0] if(matrix[0].length === 2) return ((matrix[0][0] * matrix[1][1]) - (matrix[1][0] * matrix[0][1])) sum = 0 for(i = 0; i < matrix[0].length; i++){ console.log(matrix, i) subMat = [] for(j = 1; j < matrix.length; j++) { subMat2 = [] for(k = 0; k < matrix[0].length; k++){ if(i === k) {} else subMat2.push(matrix[j][k]) } console.log(subMat2) subMat.push(subMat2) } if(i % 2 === 0) sum += matrix[0][i] * (determinant(subMat)) else sum += -(matrix[0][i] * (determinant(subMat))) console.log(subMat, sum, matrix[0].length) } return sum }
修复后的版本
//Correct code const determinantCorrected = (matrix) => { if(matrix[0].length === 1) return matrix[0][0] if(matrix[0].length === 2) return ((matrix[0][0] * matrix[1][1]) - (matrix[1][0] * matrix[0][1])) sum = 0 for(let i = 0; i < matrix[0].length; i++){ console.log(matrix, i) subMat = [] for(j = 1; j < matrix.length; j++) { subMat2 = [] for(k = 0; k < matrix[0].length; k++){ if(i === k) {} else subMat2.push(matrix[j][k]) } console.log(subMat2) subMat.push(subMat2) } if(i % 2 === 0) sum += matrix[0][i] * (determinant(subMat)) else sum += -(matrix[0][i] * (determinant(subMat))) console.log(subMat, sum, matrix[0].length) } return sum }
内容的提问来源于stack exchange,提问作者ZooQ
相关产品推荐
相关产品推荐

