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 out
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.
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:
function parent() {
var parentAsset = 'The Minivan'
function child() {
//child has access to parent asset
console.log(parentAsset);
}
// call child function
child();
}
parent(); // 'The Minivan'
function parent() {
function child() {
var childAsset = 'Mp3 Player'
}
//parent can't get childAsset
console.log(childAsset);
}
parent(); // ReferenceError childAsset not defined
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.
getFirstName('Michael')
in the example below:firstName
is set to
'Michael' nameIntro
is set it to the value "This celebrity
is "unusedString
is set to the value "This string will be garbage collected" 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.
getFirstName
function does nothing with firstName
or nameIntro
other than set their values. So there is no magic there. 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. 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.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. 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.
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 variablesgetFirstName('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'