The last two weeks, I talked about two concepts in Javascript that confuse beginners to what they actually are, but are fundamental to learning how the syntax parser in the JS engine works. The two concepts I covered were execution context and the this object. These concepts are fundamental to learning what is going on behind the scenes in your Javascript engine, because these concepts deal with how variables are declared and then defined in various cases in code. This week, I want to finish up this series with the topic of closures.
If you read my article on execution contexts, I recalled there how before learning about the fact that there were creation phases and execution phases in running Javascript code, the way I understood the idea of scope was lacking.
function outerFunction(name){
var closureVar = name
return function innerFunction(age){
console.log(`Hello my name is ${closureVar} and I am ${age} years old.`)
}
}
var executeOuter = outerFunction("Allen")
executeOuter(28)
My understanding of scope understood as much as that common definition of what is available to the inside function is also no available to the outside function. When I would look at this example, I would presume that, because the name variable was declared in as in input outside of the innerFunction, when the returned innerFunction was run, that innerFunction would have access.
But is it really that simple? In our case, there are actually two different execution contexts that are created: one for the outerfunction which returns a function, then one for the innerFunction which console logs a message with two variables. How is the outer variable accessed here?
If we recall how execution contexts work, everytime a function is executed, there is a phase where variables are declared, then assigned. In the case of the above example, the executeOuter variable is declared in the global scope, and once we get to the moment of assigning this variable, we run the outerFunction with my name as in input. This then creates another execution context which declares and assigns the variable and returns a function.
Now, the exection context for the first function is done. All the variables that were declared assigned and used in the first execution context, right? This is what I would have guessed initially as well, since you can't from the global scope now access the 'closureVar' variable that was assigned in the outerFunction's execution context. Well, to cut to the chase, you can and that's because of closures. The definition that MDN gives is this:
A closure is the combination of a function bundled together (enclosed) with references to its surrounding state
The way I like to imagine it is an imaginary box which replaces the execution context after it is finished to give access to variables to any potential recipients inside the execution context called afterwards. In our case, the outerFunction returns a function which requires access to the 'closureVar' variable inside our closure.
Now that we have covered how closures work, I would like to talk about on particular example that also defines some proper boundaries. I began to wonder how scope works in general and how if I called a function like this...
function outer1(){
var innerVar = "hello"
return innerVar
}
function outer2(var){
console.log(var)
}
outer1()
outer2(innerVar)
... outer2 function wouldn't have access to the innerVar. This is how scope normally works. That got me to wonder why that was the case for specific examples like the one I have at the very top. Well I did a little research and I found a post on StackOverflow that mentions a concept called garbage collection, which is essentially how a program gets rid of variables that it doesn't need anymore. In our case, when the outer1() execution context was created, it created, assigned, and memory dumped the innerVar.
My question was how is this any different than the 'closureVar' that we created at the top? The answer was that essentially, if a variable is no longer accessible from the outside, then it is disposed of. In our example at the top, there was a function that was returned with that still needed access to the 'closureVar'. The program then knows to leave it remaining in the closure for further access when it is called later. In the case how a classic scope works, the variables defined inside a function have no way of being accessed after that particular execution context completes, and so it knows to memory dump it.
I've really enjoyed digging deeper into these fundamental topics, because I feel like it provides a good foundation for understanding what I'm doing when I code or debug. Hopefully, it has helped you all too!
Top comments (0)