You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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列后的子矩阵行列式。

  1. 处理3×3矩阵时,递归深度只有2层,内层递归对全局i的修改,在递归返回外层时刚好还能满足循环的判断条件,循环可以走完剩余迭代,表面上看运行正常,实际上变量污染已经存在,只是结果刚好正确。
  2. 处理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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.28 05:15:42