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
The function is evaluated to be celebrityName ("Michael")("Jackson");
Steps :
- celebrityName ("Michael") returns function lastName(theLastName)
- ("Jackson") is passed to function lastName
- function lastName(theLastName) prints the string when executed
Arguments from left to right go from outer to inner called methods.
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:
- Within this function, the variable
firstName
is set to
'Michael'
- var
nameIntro
is set it to the value "This celebrity
is "
- var
unusedString
is set to the value "This string will be garbage collected"
- The function
introduceCelebrity
is declared
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?
getFirstName
must return an object or array with our variables
- we must set
getFirstName('Michael')
to a global variable mjName
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'
With this call
var mjName = celebrityName ("Michael");
you create a custom function, which has the firstName
variable bound to "Michael"
. This function is returned to you by celebrityName()
.
When you call that returned function again, you bind lastName
as well, which results in your output.
If you want to bind another first name, you have to call celebrityName()
again.
var michaelName = celebrityName( "Michael" );
var davidName = celebrityName( "David" );
michaelName( "Jackson" ); // yields "This celebrity is Michael Jackson"
davidName( "Duchovny" ); // yields "This celebrity is David Duchovny"