【问题标题】:Why isn't a variable defined in for loop not visible in setTimeout function?为什么在 setTimeout 函数中看不到 for 循环中定义的变量?
【发布时间】:2016-08-01 13:14:12
【问题描述】:

当我在控制台中运行以下代码时:

for(var k = 0; k < 36; k++){
    setTimeout(function(k){ alert(k)}, k*5000);
}

警报显示未定义。此外,我希望每次迭代后超时功能的延迟都会增加;但这不会发生。超时功能应先在 5 秒后运行,然后在 10 秒后运行,然后在 15 秒后运行,以此类推。

为什么每次迭代后都会发出 undefined 警报,为什么延迟没有增加?

由于k在timeout函数的本地范围内,所以在里面应该是可见的。

【问题讨论】:

  • k 作为函数参数隐藏了k 变量。
  • 函数中的参数k与循环的k无关,因为没有参数传递给(默认)setTimeout()回调,它将是undefined .至于时间安排:您只需将超时设置为从现在开始的 5 秒、10 秒、15 秒后触发,而不是一个接一个。
  • for(var k = 0; k &lt; 36; k++){ setTimeout(function(k){ alert(k)}, k*5000,k); }
  • @Rayon 提醒k 的正确值。但警报每 5 秒后弹出一次,而不是在 15、10、15、20...` 秒后弹出。
  • 是的..忽略该评论.. T.J提供了详细的答案...

标签: javascript function for-loop scope settimeout


【解决方案1】:

它是undefined,因为计时器机制setTimeout 将函数挂钩到不调用您提供给它的任何参数的函数(默认情况下),并且您已将k 声明为函数参数。当你调用一个参数少于它声明的参数的 JavaScript 函数时,这些参数的值是undefined。所以参数 k 隐藏(隐藏)循环变量 k,而你总是看到undefined

要修复它,不要将k 声明为函数参数:

for (var k = 0; k < 36; k++){
    setTimeout(function(){ alert(k)}, k*5000);
    // No k here -------^
}

示例(使用 500 而不是 5000):

for (var k = 0; k < 36; k++){
    setTimeout(function(){ console.log(k)}, k*500);
    // No k here -------^
}

但是,那么您将不得不解决一个新问题 (the one addressed by this question and its answers):所有这些回调看到的 k 的值将是相同 (36),因为他们看到 k 的值是在它们被调用时(稍后,一旦循环结束),而不是在它们被创建时。

在 ES5 及更早版本中,我会这样解决:

function createHandler(k) {
    return function(){ alert(k)};
}
for (var k = 0; k < 36; k++){
    setTimeout(createHandler(k), k*5000);
}

例子:

function createHandler(k) {
    return function(){ console.log(k)};
}
for (var k = 0; k < 36; k++){
    setTimeout(createHandler(k), k*500);
}

...虽然很多人会在循环中重复创建 createHandler 函数:

for (var k = 0; k < 36; k++){
    setTimeout(function(innerk) {
        return function() { alert(innerk); }
    }(k), k*5000);
}

例子:

for (var k = 0; k < 36; k++){
    setTimeout(function(innerk) {
        return function() { console.log(innerk); }
    }(k), k*500);
}

在 ES2015+(“ES6”及更高版本)中,我会这样解决:

for (let k = 0; k < 36; k++){
    setTimeout(() => { alert(k); }, k*5000);
}

...因为当您像这样在for 中使用let 时,它会为每次迭代创建一个新的k,其值不会改变。

例子:

// REQUIRES ES2015+ SUPPORT
for (let k = 0; k < 36; k++){
    setTimeout(() => { console.log(k); }, k*500);
}

【讨论】:

  • 在 ES6 中我会这样解决 for (let k = 0; k &lt; 36; k++){ setTimeout(()=&gt; alert(k), k*5000); }
  • k被声明为函数参数。为什么不弹出警报以36 作为它的值?
  • @user31782 看到条件通过了k&lt;36.. k=35 时循环结束
  • @user31782:因为函数参数 shadows(隐藏)循环变量。由于计时器没有使用任何参数调用该函数,因此您看到的参数值为undefined。但这仍然会影响 k 循环变量。
【解决方案2】:

您可以通过两种方式传递k 参数:

for(var k = 0; k < 36; k++){
    setTimeout(function(k){ alert(k); }, k * 5000, k); // but is not supported in IE9 and earlier
}

或者更好地将其包装到函数调用中:

for (var k = 0; k < 36; k++) _setTimeout(k);

function _setTimeout(k) {
    setTimeout(function(){ alert(k); }, k * 5000);
}

【讨论】:

    【解决方案3】:

    您将 k 传递给 setTimeOut 的回调函数,但它什么都不带。因此删除参数 k 将起作用。

    for(var k = 0; k < 36; k++){
    setTimeout(function(){ alert(k)}, k*5000);
    }
    

    【讨论】:

    • 如果不将 k 值包装或绑定到 setTimeout 回调,您将共享他的范围,它将使用 k的当前值>,并且警报将始终显示“36”(参见完整示例:stackoverflow.com/questions/750486/…)。
    • 我没有投反对票。尽管您的解决方案在每次迭代后打印36。我们需要函数闭包来保存k的不同值。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-03-11
    • 2021-03-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多