Monthly Archives: March 2015

Mystery of javascript Closures

What is a closure?
A closure is an inner function that has access to the outer (enclosing) function’s variables—scope chain. The closure has three scope chains: it has access to its own scope (variables defined between its curly brackets), it has access to the outer function’s variables, and it has access to the global variables.

The inner function has access not only to the outer function’s variables, but also to the outer function’s parameters. Note that the inner function cannot call the outer function’s arguments object, however, even though it can call the outer function’s parameters directly, below is basic example

function showFullName(firstName, lastName) {
    var statementPrefix = "Your name is ";
    // this inner function has access to the outer function's variables, including the parameter​ 
    function makeFullNameStatement() {
        return statementPrefix + firstName + " " + lastName;
    }
    return makeFullNameStatement();
}
showFullName("Aamol", "Gote"); // Your name is Aamol Gote
  • Closures have access to the outer function’s variable even after the outer function returns:
    One of the most important and ticklish features with closures is that the inner function still has access to the outer function’s variables even after the outer function has returned. Yep, you read that correctly. When functions in JavaScript execute, they use the same scope chain that was in effect when they were created. This means that even after the outer function has returned, the inner function still has access to the outer function’s variables. Therefore, you can call the inner function later in your program. This example demonstrates:
function appreciatePerson(firstName) {
    var personAppreciation = "You are awesome!!!";
    // this inner function has access to the outer function's variables, including the parameter​
    function getPersonAppreciation(lastName) {
        return personAppreciation + " " + firstName + " " + lastName;
    }
    return getPersonAppreciation;
}
// At this juncture, the appreciatePerson outer function has returned.
var firstNameAppreciation = appreciatePerson("Aamol");

// The closure (getPersonAppreciation) is called here after the outer function has returned above​ 
// Yet, the closure still has access to the outer function's variables and parameter 
firstNameAppreciation("Gote"); //This will return: You are awesome!!! Aamol Gote
  • Closures store references to the outer function’s variables; They do not store the actual value. 
Closures get more interesting when the value of the outer function’s variable changes before the closure is called. And this powerful feature can be harnessed in creative ways, such as this private variables
    function personIQ() {
        var personIQLevel = 700;
        // We are returning an object with some inner functions​ 
        // All the inner functions have access to the outer function's variables 
        return {
            getIQ: function () {
                // This inner function will return the updated personIQLevel variable​ 
                return personIQLevel;
            },
            setIQ: function (iq) {
                // This inner function will change the outer function's variable anytime​
                personIQLevel = iq;
            }
        }
    }
    
    var iq = personIQ();
    iq.getIQ(); //Returns 700;
    iq.setIQ(900); // Changes the outer function's variable​ 
    iq.getIQ(); // 900: It returns the updated personIQLevel variable
    
  • Closures Issues Resolved with help of IIFE ( Immediately Invoked Function Expression): Because closures have access to the updated values of the outer function’s variables, they can also lead to bugs when the outer function’s variable changes with a for loop. Example demonstrates below:
    function assignIQToScientist(scientists) {
        var personDefaultIQLevel = 700;
        var i;
        for (i = 0; i < scientists.length; i++) {
            scientists[i]["iqLevel"] = function () {
                return personDefaultIQLevel + (i * 50);
            }
        }
        return scientists;
    }
    var scientists = [{ Name: "Isaac Newton", iqLevel: 0 }, { Name: "Alva Edison", iqLevel: 0 }, { Name: "Albert Einstein", iqLevel: 0 }]
    var scientistsWithIQ = assignIQToScientist(scientists);
    var newtonIQ = scientistsWithIQ[0];
    console.log(newtonIQ.iqLevel()); //Outputs 850
    var edisonIQ = scientistsWithIQ[1];
    console.log(edisonIQ.iqLevel()); //Outputs 850
    var einsteinIQ = scientistsWithIQ[2];
    console.log(einsteinIQ.iqLevel()); //Outputs 850
    

    In the preceding example, by the time the anonymous functions are called, the value of i is 3 (the length of the array and then it increments). The number 3 was added to the personDefaultIQLevel to create 850 for ALL the scientist ID’s. So every position in the returned array get IQ Level = 850, instead of the intended 700, 750, 850.

    The reason this happened was because, as we have discussed in the previous example, the closure (the anonymous function in this example) has access to the outer function’s variables by reference, not by value. So just as the previous example showed that we can access the updated variable with the closure, this example similarly accessed the i variable when it was changed, since the outer function runs the entire for loop and returns the last value of i, which is 850.

    To fix this side effect (bug) in closures, you can use an Immediately Invoked Function Expression (IIFE), such as the following:

    function assignIQToScientist(scientists) {
        var personDefaultIQLevel = 700;
        var i;
        for (i = 0; i < scientists.length; i++) {
            scientists[i]["iqLevel"] = function (i) {
                return personDefaultIQLevel + (i * 50); // each iteration of the for loop passes the current value of i into this IIFE 
                //and it saves the correct value to the array​
            }(i);// immediately invoke the function passing the i variable as a parameter.  BY adding () at the end of this function, 
            //we are executing it immediately and returning just the value of personDefaultIQLevel + (i * 50), instead of returning a function.​
        }
        return scientists;
    }
    var scientists = [{ Name: "Isaac Newton", iqLevel: 0 }, { Name: "Alva Edison", iqLevel: 0 }, { Name: "Albert Einstein", iqLevel: 0 }]
    var scientistsWithIQ = assignIQToScientist(scientists);
    var newtonIQ = scientistsWithIQ[0];
    console.log(newtonIQ.iqLevel); //Outputs 700
    var edisonIQ = scientistsWithIQ[1];
    console.log(edisonIQ.iqLevel); //Outputs 750
    var einsteinIQ = scientistsWithIQ[2];
    console.log(einsteinIQ.iqLevel); //Outputs 800
    
Advertisements