Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Javascript closures function parameters?

Code belongs to javascriptissexy.com My question is why invoking mjName ("Jackson") returns "This celebrity is Michael Jackson"? Is it that second parameter given in ANY outer function always, says to js = inner function parameter? Could someone explain the whole concept in great detail?

function celebrityName (firstName) {
    var nameIntro = "This celebrity is ";
// this inner function has access to the outer function's variables, including the parameter
    function lastName (theLastName) {
        return nameIntro + firstName + " " + theLastName;
    }
    return lastName;
}

var mjName = celebrityName ("Michael");
    // At this juncture, the celebrityName outer function has returned.

// The closure (lastName) is called here after the outer function has returned above
// Yet, the closure still has access to the outer function's variables and parameter
mjName ("Jackson"); // This celebrity is Michael Jackson
like image 657
user2067358 Avatar asked Sep 06 '13 10:09

user2067358


2 Answers

The OP asked for an explanation of the Whole concept in detail. The attempt here is to describe the core elements that are necessary for closures to occur.

I think that part of the confusion with examples like the one from javascriptissexy is that the names of these functions do not clearly represent what they are supposed to do, especially to someone who is new to javascript or new coding in general.

Lets start by talking about scope:

In Javascript, every function creates its own local scope or memory space. This is called Lexical Scoping. This memory space stores all of the variables from the functions's parameters as well as the declared variables and expressions within the function body (inside the curly braces).

As seen in the example from javascriptissexy, we can nest functions. Since each function creates its own local scope, we need to understand how these scopes relate and interact with each other. There are three different types of relationships that scopes can have.

I would encourage you to test all of these code snippets inside your browser dev console:

Child scopes have access to their parent's (and grandparent's, great grandparent's etc...) scope variables

    function parent() {

        var parentAsset = 'The Minivan'

        function child() {

            //child has access to parent asset

            console.log(parentAsset);

        }

        // call child function

        child();
    }

    parent();  // 'The Minivan'

Parent scopes DO NOT have access to their children's scope variables

    function parent() {

        function child() {

            var childAsset = 'Mp3 Player'

        }

        //parent can't get childAsset

        console.log(childAsset);

    }

    parent();   // ReferenceError childAsset not defined

Sibling scopes DO NOT have access to each other's scope variables

    function childOne() {

        var childOneAsset = 'Iphone'

    }

    function childTwo() {

        console.log(childOneAsset);

    }

    childTwo();  // ReferenceError childOneAsset not defined

Okay, so back to the function mentioned by OP. Let's try to remake this function with better names. I am adding one more variable to this first example function to show a point.

Here are 4 things that happen when you call getFirstName('Michael') in the example below:

  1. Within this function, the variable firstName is set to 'Michael'
  2. var nameIntro is set it to the value "This celebrity is "
  3. var unusedString is set to the value "This string will be garbage collected"
  4. The function introduceCelebrity is declared
  5. The function introduceCelebrity is returned

    function getFirstName (firstName) {
    
        var nameIntro = "This celebrity is ";
        var unusedString = "This string will be garbage collected";
    
        function introduceCelebrity (lastName) {
            return nameIntro + firstName + " " + lastName;
        }
    
        return introduceCelebrity;
    }
    
    var mjName = getFirstName('Michael');
    

You probably already knew that.

Here are some interesting things to note:

  • The getFirstName function does nothing with firstName or nameIntro other than set their values. So there is no magic there.
  • The child function introduceCelebrity references those two variables. As mentioned before, it can do that because children scopes can access parent scope variables. This is the first important step to a closure.
  • The introduceCelebrity function is then returned (but not executed), presumably so we can call it at a later time. This is the second step to a closure.
  • Because introduceCelebrity references parent scope variables, and we return the whole function, the javascript runtime maintains a pointer to those variables, even after the getFirstName function returns.
  • Because that pointer exists, the garbage collector leaves those variables alone. If those pointers didn't exist, the garbage collector would come through and clean those memory addresses and those values would be inaccessible.
  • The unusedString variable is not referenced in the child function, therefore it is garbage collected and is no longer available.

So let's look at the code again:

function getFirstName (firstName) {

    var nameIntro = "This celebrity is ";

    function introduceCelebrity (theLastName) {
        return nameIntro + firstName + " " + theLastName;
    }
    return introduceCelebrity;
}

var mjName = getFirstName('Michael');

When this code executes, we are basically doing this:

var mjName = function(theLastName) {
    return nameIntro + firstName + " " + theLastName;
}

What is special about this? Where is the closure?

Because our getFirstName function has been executed, we might think that the whole thing has gone away along with its local variables or assets. THIS IS INCORRECT.

We created a closure by referencing parent scope variables inside a child function and returning the child function. So really, the new scope of the code right above actually looks more like this:

var nameIntro = "This celebrity is ";

var firstName = "Michael"

var mjName = function(theLastName) {
    return nameIntro + firstName + " " + theLastName;
}

See how nameIntro and firstName are now available to us? THAT IS BECAUSE WE CREATED CLOSURE.

So now we call mjName:

mjName('Jackson');  // 'This celebrity is Michael Jackson'

And we get the result expected.

Wait, One last thing!

To really drive the point home, lets compare our example to a slightly modified one.

Notice the original function is nested. Closures only happen with nested functions.

function getFirstName (firstName) {

    var nameIntro = "This celebrity is ";

    function introduceCelebrity (theLastName) {
        return nameIntro + firstName + " " + theLastName;
    }
    return introduceCelebrity;
}

var mjName = getFirstName('Michael');

Let's try removing that nesting:

function getFirstName (firstName) { 
    var nameIntro = "This celebrity is ";
}

function introduceCelebrity (theLastName) {
    return nameIntro + firstName + " " + theLastName;
}

var mjName = getFirstName('Michael');
introduceCelebrity('Jackson');  

// ReferenceError: nameIntro is not defined

Would this work?

No, it wouldn't. Because sibling scopes can't access each other's variables.

How could we get this to work without a closure then?

  1. getFirstName must return an object or array with our variables
  2. we must set getFirstName('Michael') to a global variable mjName
  3. Call introduceCelebrity('Jackon'), passing in the values we mjName

    function getFirstName (firstName) { 
    
        var nameIntro = "This celebrity is ";
    
        return {
            firstName: firstName,
            nameIntro: nameIntro
        }
    }
    
    var mjName = getFirstName('Michael');  // returns our object
    
    function introduceCelebrity (theLastName, firstName, nameIntro) {
        return nameIntro + firstName + " " + theLastName;
    }
    
    introduceCelebrity('Jackson', mjName.firstName, mjName.nameIntro);  
    
    // 'This celebrity is Michael Jackson'
    
like image 52
kurtcorbett Avatar answered Oct 07 '22 04:10

kurtcorbett


The function is evaluated to be celebrityName ("Michael")("Jackson");

Steps :

  1. celebrityName ("Michael") returns function lastName(theLastName)
  2. ("Jackson") is passed to function lastName
  3. function lastName(theLastName) prints the string when executed

Arguments from left to right go from outer to inner called methods.

like image 21
user568109 Avatar answered Oct 07 '22 03:10

user568109