javascript for-loop和超时功能

时间:2017-02-07 16:54:49

标签: javascript callback timeout

我遇到了运行setTimeout的for循环问题。

for (var x = 0; x < 5; x++) {
    var timeoutFunction = function() {
        return function() {
            console.log(x)
        }
    }
    setTimeout(timeoutFunction(), 1)
}

我期待输出

  

0

     

1

     

3

     

4

然而,出于某种原因,他们都输出了5。

变量x在for循环的本地范围内定义,所以我认为这可能不计入setTimeout的回调。我测试了在for循环之外定义x

var x = 10
for (var x = 0; x < 5; x++) {
    var timeoutFunction = function() {
        return function() {
            console.log(x)
        }
    }
    setTimeout(timeoutFunction(), 1)
}

我认为这个输出会给出10,但它没有。然后我认为之后定义x是有意义的。

for (var x = 0; x < 5; x++) {
    var timeoutFunction = function() {
        return function() {
            console.log(x)
        }
    }
    setTimeout(timeoutFunction(), 1)
}
var x = 10

这只返回10.这意味着回执都是在执行for循环后调用的?为什么一旦在执行for循环后初始化变量,它们只符合for循环的父作用域?我错过了什么吗?

我知道如何使这个例子与

一起使用

for (var x = 0; x < 5; x++) {
    var timeoutFunction = function(x) {
        return function() {
            console.log(x)
        }
    }
    setTimeout(timeoutFunction(x), 1)
}

然而,我真的很想知道遗失了什么......

3 个答案:

答案 0 :(得分:1)

您必须为您的功能创建新的范围,以便让它记住&#39;来自给定迭代的值:

for (var x = 0; x < 5; x++) {
    var timeoutFunction = (function(x) {
        return function() {
            console.log(x)
        }
    })(x)
    setTimeout(timeoutFunction(), 1)
}

另一种解决方案是使用ES2015 let

for (let x = 0; x < 5; x++) {
    var timeoutFunction = function() {
            console.log(x)
        }
    setTimeout(timeoutFunction(), 1)
}

答案 1 :(得分:1)

使用此代码段

for(let x = 0; x < 5; x++) {
    var timeoutFunction = function() {
        console.log(x);
    };
    setTimeout(timeoutFunction, 1);
};
console.log('For loop completed');

JavaScript不是多线程的,因此对于你的代码,timeoutFunction将在for循环完成后执行,因为你正在使用全局变量(关于timeoutFunction上下文),打印最终结果

答案 2 :(得分:1)

请注意,将1指定为延迟值实际上不会导致函数在1毫秒后执行。函数执行的最快速度大约是9毫秒(这是基于浏览器的内部结构),但实际上,在没有其他代码运行之前,它无法运行该函数。

对于意外输出,您遇到此行为,因为timeoutFunction包含对在更高范围内声明的x变量的引用。这会导致在x变量周围创建 closure ,这样每次运行该函数时,它都不会获得x的副本,但是它正在共享x值,因为它是在更高的范围内声明的。这是闭包的经典副作用。

有几种方法可以调整语法来解决问题......

制作x的副本,让每个函数使用自己的副本,方法是将x传递给函数。传递基本类型(布尔值,数字,字符串)时,会创建一个数据副本,这就是传递的内容。这打破了对共享x范围的依赖。你的最后一个例子是这样的:

for (var x = 0; x < 5; x++) {
    var timeoutFunction = function(x) {
        return function() {
            console.log(x)
        }
    }
    // Because you are passing x into the function, a copy of x will be made for the
    // function that is returned, that copy will be independent of x and a copy will
    // be made upon each loop iteration.
    setTimeout(timeoutFunction(x), 1)
}

如果你的超时函数没有返回另一个函数(因为没有函数将值传递给),那么需要做同样事情的另一个例子。因此,此示例创建了一个额外的函数:

for (var x = 0; x < 5; x++) {
  
    // This time there is no nested function that will be returned,
    function timeoutFunction(i) {       
            console.log(i);        
    }
    
    // If we create an "Immediately Invoked Funtion Expression" (IIFE),
    // we can have it pass a copy of x into the function it invokes, thus
    // creating a copy that will be in a different scope than x.
    (function(i){        
      setTimeout(function(){
        timeoutFunction(i);  // i is now a copy of x
      }, 1);
    }(x));
}

如果您使用的是支持ECMAScript 2015标准的浏览器,您只需将循环中的var x声明更改为 let x ,即可{{1}在每次循环迭代时获取块级范围:

x