Hi, I am Calin, the author of this article and a Full-Stack Software Developer with a track record of continuously improving and applying technical know-how to deliver high quality, user-focused solutions. Being a natural problem-solver my goal with this article was to shed some light on this controversial topic and to give a clear short explanation.Calin Iordache
Even though we can’t use “type of” to put our fingers on a type of data, we can use Chrome DevTools to get some guidance. To move we need some code, and I decided to use the one example we find everywhere, so maybe you’ve seen this before and you’re familiar with it.
If you take a quick look over this, and you’ve previously researched some, you should notice the so-called “closure”. Now we’re gonna move on to the DevTools, and look for our function.
After another quick look, we can already half-answer our question. The closure is a SCOPE.
Any function can access global variables, but global functions cannot access local or private variables, so we actually have two usages for this, one would be restraining access to a global variable to move it to a local/private context, and one would be giving global access to a variable that otherwise would have short lifecycle or usage. If you look into the code above, we can have access to the “counter” variable in our global scope, even though it is declared with let and it should be accessible at block-level only. This is because when we returned the function in getCounter(), we returned both its variables(which do not exist in the current example), as well as the references to the parent function. The confusion appears when one refers to any of the elements above as “closure”.
The closure is the function’s scope that contains the variables declared in its parent function.
Even though we have a simple answer that is hard to prove wrong, it is crucial to understand the whole context and every reason this scope is created, so we can get rid of any sign of doubt. To do this we have to understand 3 more abstract terms:
Every time a function is called, it creates an execution context, which adds up in a LIFO (Last In First Out) type of stack. Our first stack is added when our script is loaded. This first stack is called GLOBAL CONTEXT. Over this context, we add the context created by a child function, and over that one, we add its child function and so on.
Having the code above, we’d have the following execution contexts:
== Global Context
======veryLocalExec Context ends
====localExec Context ends
== Global Context ends when the script is no longer available
So far so good. When we execute a function we create its context which has 2 phases.
Phase 1: Creation phase, where we store all the declarations (and their reference) in memory, then we add undefined value to some of them (more about this in future hoisting article).
Phase 2. Execution phase, where we assign the values to the variables and execute the function
Because we have to understand the first step, we’ll get into more details here. In the creation step we can split the context into 3 things:
2. VariableEnvironment. This is where we store the declarations.
3. Lexical Environment. In the Creation phase, this is just a duplicate of VariableEnvironment.
This environment is the part of the context that will keep reference with the LexicalEnvironment from parents Execution Context. Based on this we can split the LexicalEnvironment into 2 parts as well:
1. EnvironmentRecord (The VariableEnvironment duplicate)
The process in which an identifier is found is called “Scope chain look-up” or “Identifier Resolution”.
This process starts from the execution of the current context (from the top of the stack) and searches its LexicalEnvironment in EnvironmentRecord. If our identifier was not found, search for the reference and go to the parent LexicalEnvironment. This process is repeated until the identifier is found or was not even found on the global scope.
In the example “Counter” above, the process is as follows (I recommend you open the image with the code so you can follow).
– Global scope is created
– When assigning getCounter (), the process of creating the Execution context of the new “count” function is done.
– When calling count () the LexicalEnvironment also creates references from the Global Context
– The return function creates and calls an Execution context, this means that the Lexical Environment also creates its references.
a) “counter” is searched in its Environment, is not found, but has a reference on the parent Environment, where it has the value 0 and is returned.
b) “counter” is incremented by ++ and becomes 1, IN the context of the “count” function FROM the context of the return function.
Steps a and b are repeated 2 more times, thus returning the values 0 1 and 2.
I recommend you analyze the following situation, write down the Contexts as we did above and try to understand what will appear in the console after the following lines of code, WHERE AND IF we have closure.