简体   繁体   中英

Closure confusion

I am having some confusion regarding this Closure thing. I have two separate codes below that look similar but their output are different.

function setup(x) {
var array = [];
for(var i=0;i<arguments.length;i++){
    array[i]= arguments[i];
}
return array;
}
console.log(setup('a','b'));  // will output ["a","b"] 

--------------
function f() {
var i, array = [];
for(i = 0; i < 3; i++) {
    array[i] = function(){
        return i;
    }
}
return array;
}

var a = f();                 
console.log(a());       //output: [function(),function(),function()]
console.log(a[0]());    //output: 3 //same output in a[1]() and a[2]() calls as well

Now my question is, how come the output is different? both of the codes above return an array. in the first code, it prints all elements in array correctly whereas in the second code, why doesn't it print [1,2,3]???

In your second example, you are creating 3 function in a loop, but all the functions are created in the same variable scope, so they all reference and return the value of the same i variable.

Therefore the value of i returned from the functions represents the value at the time when the functions are invoked. Because you're invoking them after the loop, the value of i is 3 , so that's the value returned.

This is what is meant by closure. The functions "close over" the variables that existed in the variable scope where they were created. They do not close over the value of the variables, but rather then variables themselves, so they always get the current state of the variable.

For each function to reference a different value of i , each function would need to need to be created in a separate variable scope that has its own unique i .

Because the only way to create a new variable scope in JavaScript is to invoke a function, you'll need to create each function within a new function invocation.

function makeFunction(j) {
    return function(){
        return j;
    };
}

function f() {
    var i, array = [];
    for(i = 0; i < 3; i++) {
        array[i] = makeFunction(i);
    }
    return array;
}

So here I made a new function called makeFunction . It receives a single parameter, and returns a new function that references and returns that parameter.

Because each invocation of makeFunction creates a new and unique variable scope, each function returned will be referencing its own unique j variable, and will therefore return the value of j that existed when makeFunction was invoked (unless your function modifies j , which it could do if you wanted) .

Note that I used the variable name j for clarity. You could use i as well, or some other name.

In JavaScript, you have function statements and function expressions . The first declare named functions, the latter evaluate to a named or anonymous function. You're using a function expression.

What I think you want to do is a call expression. All you have to do is to immediately call your function that returns i .

function f() {
    var i, array = [];
    for (i = 0; i < 3; i++) {
        // The extra parentheses around the function are unnecessary here.
        // But this is more idiomatic, as it shares syntax with how function
        // expressions are introduced in statements.
        // I.e. you could just copy-paste it anywhere.
        array[i] = (function () {
            return i;
        })(); // don't rely on automatic semicolon insertion
    }
    return array;
}

Also note that, in your problematic example, all closures return 3 because all of them capture the same variable i .

EDIT: To make the previous paragraph more clear, if you really wanted to have 3 distinct closures, you'd have to create a new scope for each one. Unlike other languages, Javascript doesn't create scope just by opening a block. It only creates scope in functions. Here are two ways of doing this:

function f() {
    var i, array = [];
    for (i = 0; i < 3; i++) {
        // With a parameter in an inner function
        array[i] = (function (n) {
            return function () {
                // A new n is captured every time
                return n;
            };
        })(i);
    }
    return array;
}

function f() {
    var i, array = [];
    for (i = 0; i < 3; i++) {
        array[i] = (function () {
            // With a variable in an inner function
            var n = i;
            return function () {
                // A new n is captured every time
                return n;
            };
        })();
    }
    return array;
}

The next example, however, is wrong , because even though n is declared in the for block, it will be as if it has been declared at the top of the function and only initialized in the for block. Remember, this is JavaScript, not Java, not C, not C#, not <whatever bracketed language>:

function f() {
    var i, array = [];
    for (i = 0; i < 3; i++) {
        var n = i;
        array[i] = function () {
            return n;
        };
    }
    return array;
}

Equivalent code:

function f() {
    var i, array = [];
    var n;
    for (i = 0; i < 3; i++) {
        n = i;
        array[i] = function () {
            return n;
        };
    }
    return array;
}

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