简体   繁体   中英

javascript for-loop and timeout function

I am facing an issue with a for-loop running a setTimeout.

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

I expect the output

0

1

3

4

Yet, for some reason they all output 5.

Variable x is defined in the local scope of the for-loop, so I thought this may not count for the callback of setTimeout. I tested with defining x outside of the for-loop.

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

I figured this output would be giving 10, yet it didn't. Then I thought it would make sense to define x afterwards.

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

This does return only 10. This implies the callbacks are all called after the for-loop is executed? And why do they only conform to the parent scope of the for-loop once the variable is initialised after execution of the for-loop? Am I missing something?

I know how make this example working with

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

Yet, I really wonder what is missing...

You have to create new scope for your function to make it 'remember' value from given iteration:

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

Another solution is to use ES2015 let :

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

use this snippet

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

JavaScript is not multi threaded so with your code the timeoutFunction will execute after the for loop is completed since you are using the global variable (with respect to timeoutFunction context) the final result is printed

Note that specifying 1 as the delay value will not actually cause the function to execute after 1 millisecond. The fastest the function could execute is roughly 9 milliseconds (and that's based on the internals of the browser), but in reality, it can't run the function until there is no other code running.

As for the unexpected output, you are experiencing this behavior because the timeoutFunction includes a reference to the x variable that is declared in a higher scope. This causes a closure to be created around the x variable, such that each time the function runs it does not get a copy of x for itself, but it is sharing the x value because it is declared in a higher scope. This is the classic side effect of closures.

There are a few ways to adjust the syntax to fix the issue...

Make a copy of x and let each function use its own copy by passing x into the function. When you pass a primitive type (boolean, number, string) a copy of the data is created and that's what is passed. This breaks the dependence on the shared x scope. Your last example does this:

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

Another example that does the same thing would be needed if your timeout function wasn't returning another function (because there would be no function to pass the value to). So, this example creates an extra function:

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

If you are working with browsers that support the ECMAScript 2015 standard, you can simply change the var x declaration in your loop to let x , so that x gets block level scope upon each iteration of the loop:

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM