javascript for循环和超时函数

javascript for-loop and timeout function

我遇到了 for-loop 运行 setTimeout 的问题。

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

这 return 只有 10 个。这意味着回调都在 for 循环执行后调用?为什么在执行 for 循环后初始化变量后,它们只符合 for 循环的父范围?我错过了什么吗?

我知道如何让这个例子与

一起工作

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

然而,我真的很想知道缺少什么...

您必须为您的函数创建新范围,使其成为给定迭代的 'remember' 值:

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)
}

使用这个片段

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 上下文)最终结果被打印出来

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

至于意外输出,您遇到此行为是因为 timeoutFunction 包含对在更高范围内声明的 x 变量的引用。这会导致围绕 x 变量创建 closure,这样每次函数 运行s 它都不会获得 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,因此x 在循环的每次迭代中获得块级范围:

// Declaring a variable with let causes the variable to have "block-level"
// scope. In this case the block is the loop's contents and for each iteration of the
// loop. Upon each iteration, a new "x" will be created, so a different scope from 
// the old "x" is what's used.
for (let x = 0; x < 5; x++) {
    var timeoutFunction = function() {
        return function() {
            console.log(x)
        }
    }
    setTimeout(timeoutFunction(), 1)
}