JavaScript Functions and Closures — How They Work.

Aditya Loshali
The Startup
Published in
13 min readJan 2, 2021

--

This writeup talks about three JavaScript concepts which are commonly referred to as advanced JavaScript concepts but are not only fundamental but also easy to understand: Execution Context, Lexical Environment, and Closure.

First, let’s talk about some basic prerequisites.

JavaScript is a function-oriented language and as such it is very flexible with how functions can be used. In JavaScript the functions are first-class citizens or also called first-class objects. Which simply means that you can store functions in variables, pass them to other functions as arguments, and return them from other functions as values, just like objects.

First-Class Functions
First — Class Functions (store in variable or send in argument)
First — Class Functions (returning function from another function)

This last bit of code may seem quite confusing right now because the returned function stored in the variable addTen somehow remembers the argument a even after the parent function add was executed and removed from the call stack. So somehow the state persisted. But we don’t know how, not yet. This is possible because of closures.

So now, we’ll learn different concepts related to closure, and then, at last, understand what a closure is.

What do the terms Lexical Environment and Execution Context mean ?

Lexical Environment or the Static Scope is an internal Data Structure managed by the JavaScript runtime which holds the identifier to variable mapping of arguments, variables and functions which are either part of the global top level code or of a function code, and is like a normal object with properties.

So this is where variables, functions stored in memory have their references attached during execution.

Each Lexical Environment object has two parts:

  1. An object Environment Record, where the declared variables and functions are present.
  2. A reference to the outer lexical environment.

So for the code,

The lexical environment mapping, at some point in time during execution, would look like.

Lexical Environment

I said at some point in time, because the values of Environment Record of the the Lexical Environment get added or change over time function is executing after being invoked (Environment Record is mutated by the active Execution Context, which we’ll learn shortly).

Only the outer reference is static.

The term Lexical in Lexical Environment means how and where your code is physically present within the written source code / where in the script it was defined. Hence, also called as the static scope based on where the function was defined.

A new Lexical Environment structure is created each time JavaScript parser creates a function (or parses the global code in the script), and the reference to the outer lexical environment is bound based on the location of the functions code within the script. We’ll see examples for this, later.

So due to this Static scope, we know at the function creation time, what the scope of variables of a function is (weather they are a part of the functions own scope — code inside {} braces or of a surrounding parent scope).

JavaScript uses this outer parent reference when it can not find a property in the current Lexical Environment record. If it still can’t find it in the parent Lexical Environment, then it, again, goes up to the further parent Lexical Environment. This process doesn’t stop until it finds what it’s looking for or until the outer reference points to null.

So, for the code example below,

Nested function scopes.

When this code runs, x and foo are stored in the global top most lexical environment, and it’s outer pointer points to nothing as there is no parent lexical environment. Then when the parser processes the definition of foo, a new lexical environment for foo is created. And, it’s outer pointer points to the global lexical environment. and this process repeats for all nested functions.

When the code is then executed after processing the declarations. For variable z, JavaScript looks into the lexical environment of baz. z is not found there, so then JavaScript moves up to look in the next environment pointed by outer, the one of bar, and z is found there.

For variable w, moving up the chaining of lexical environments to the top, w is not found anywhere, therefore the Reference Error.

Execution Contexts with Lexical Environment links.

Now, the examples related to the significance of term Lexical.

Since Lexical Environment or Static Scope is created at function Creation Phase. Therefore, the place in code where we define the function is what decides the variables that the function has defined when it’s called, due to location of the variables in memory being decided at creation phase.

Outputs — X is 20

That’s why in the above function, output is X is 20. Because, the place of invocation when function is called does not matter. What matters is what was captured where the function was declared.

Because the outer lexical environment reference for foo was established when it was declared within the top level code. So, during it’s execution when x is not found inside of it’s own environment record, it is referred from the outer scope.

Lexical Environment is a specification object: it only exists “theoretically” in the language specification to describe how things work. We can’t get this object in our code and manipulate it directly.

Now that we know what Lexical Environment is let’s understand the next related concept, the Execution Context.

What is the Execution Context ?

Context can be visualized as a Circle or a Wrapper which surrounds the facts related to an event. In code, it’s the data related to a function.

Execution Context is also an internal structure composed of Lexical Environment, and it refers to the code that is currently running and all the data surrounding to that is helping in running it.

Conceptually an Execution Context has the following structure.

Execution Context

Where Lexical Environment is the same concept / structure we talked about earlier in this writeup.

And This Binding is this property—a variable with the value of the object that invokes the function where this is used. There’s more to it, but it is a separate topic which requires it’s own detailed writeup. So, I won’t discuss it here.

There is also a third part of the execution context type, the Variable Environment. But it’s not shown here because for most cases it’s just like a copy of the Lexical Environment. Check this article for more info.

On invocation of each function or parsing of an imported script. A new Execution Context is created, and when execution begins, the executing Execution Context is put on top of the stack.

When the global code (the code at topmost level) is executed, it’s executed inside the global execution context, and the function code is executed inside the function execution context.

There can be many different Lexical Environment belonging to different functions, but at a time there can only be one currently running Execution Context (Because JavaScript is single threaded language), while rest are paused. This is managed by a stack data structure known as Execution Stack or Call Stack.

The currently running code of a function is the currently active execution context. When this code runs to it’s completion, the active execution context of this function is popped off the stack, and the control reaches to the execution context below it. Or in other words, the previous function which invoked this function.

Each Execution Context has two phases.

  1. Creation Phase — This is where the code for the function or script is parsed / created by JavaScript runtime. The Lexical Environment belonging to the code is included. Where variables, arguments and function variables are declared and initialized (with defaults) and memory is reserved for them. And the This Binding is also decided in this phase.
  2. Execution Phase in this phase the JavaScript engine executes/runs the code line by line and values are assigned to function arguments and variables in the specific environment.

So if we talk about this script.

When your script initially runs, JavaScript starts parsing the script. A Global Execution Context is created and a corresponding Lexical Environment record.

And whenever the global execution context is created, a global this is created, and another global object is created, which is window for browser. This happens at the start of the script before anything, and just for the Global Execution Context ( GEC ).

On, running this code the output is.

What is going on here ?

Actually JavaScript first parses (creation phase of execution context) the top level code, and then it executes (execution / active phase of execution context).

First the creation phase of GEC ( the run through by JS), JavaScript updates the Lexical Environment structure, parsing the code line by line. And the code may be hoisted, depending on how variables were declared.

Now what’s that ?

Hoisting

If you have a variable declared with var or function declaration, JavaScript will hoist it or allocate memory for it and move the declarations to the top of their scope while parsing during the first run through, before execution.

So, after parsing, and before execution. Your code internally becomes like.

Hositing

Hoisting is JavaScript’s default behavior of moving all declarations to the top of the current scope (to the top of the current script or the current function).

Variables defined with let and const are not hoisted and cannot be used until it has been declared.

Using a let variable before it is declared will result in a ReferenceError.

Please note that the example image of hoisted code also shows hoisting done for the code inside function. but that does not happens in first parsing for GEC but when the Function Execution Context is created while parsing function code after it’s called.

Execution Context continued

OK ! . Back to the GEC (parsing)

  1. All variables are declared with value equal to undefined, and memory gets allocated for variables, therefore variable aName is initialized with value = undefined .

2. All functions are declared with the whole definition, and placed into the memory, so that happens for logName. Function Lexical Environment is also created and linked to Global Lexical Environment outer reference for the functions declared in global top level scope (at this point Environment Record is empty as functions own code hasn’t been parsed, just the link to parent environment is fixed).

This process of declaring variables and functions and placing them at the top before executing the code line by line is due to hoisting.

A reference to an outer lexical environment is created. In other words, the scope chain is created which is used to resolve variables. For global execution context it is null.

JavaScript runtime sets the value of this . Which is equal to window object for Global Execution Context.

At this point after the creation phase is over the script was just parsed by the JavaScript and not executed. The GEC (Global Execution Context) and the Environment Record right now looks like.

GEC — Execution Context Creation Phase

After the creation phase of GEC is over, JavaScript starts execution of the script. So, the execution phase of GEC starts and the currently executing context is pushed to the stack.

In this phase the values of the variables are assigned. therefore variable aName is assigned the value = “JavaScript”

GEC — Execution Context Execution Phase

Up until now on the stack we only had the GEC. GEC is a special Execution Context which is always present, as long as the script is running, as it’s the bottom most context in the stack.

Then the function logName is invoked. Now the Function Execution Context is created (creation phase), parsing starts, and the Function Execution Context, before execution begins, looks like.

Then the execution context with the Lexical Environment reaches the execution phase, and is pushed on top of the stack over the previously active GEC, and the execution of GEC is paused for now.

And during execution the Environment Record is updated when a new value is assigned to anotherName and the execution context looks like.

Here’s the flow in a GIF

This is what I meant earlier when I said that ‘at some point in time’ Environment Record (Lexical Record) may look different from some other point in time. Active Execution Context keeps on making changes to it.

During it’s execution, the function logName looks up the aName variable in it’s own Lexical Environment first. When not found, it travels to the upper scope through the outer lexical environment link captured during the creation phase and finds the variable there. This travelling up the chain of scopes through the lexical environment links is known as Scope Chain.

It prints the resolved value, and execution phase of logName is over. So it’s executed context is popped off the stack. The unreferenced lexical environment is destructed, and variable memory is handled by JavaScript Garbage Collection.

And the paused GEC resumes. Since, there are no more actions to take care of now and no active event listeners. GEC is also popped of the stack and a process similar to what happened to logName is done for GEC. And the script execution stops.

Now that we understand the creation and execution process of JavaScript functions and associated concepts Functions as First-Class Citizens, Lexical Environment and Execution Context. It’s time to learn the last concept which is possible due to the earlier concepts. The JavaScript Closures . Yay !!

What is a Closure?

A closure is a function that has access to its outer function scope even after the outer function has returned (finished executing). This means a closure can remember and access variables and arguments of its outer function even after the function has finished.

These are stateful functions which can remember and persist the data from a parent function through multiple executions of the function. Because they closed over the data, and hence have access to the parent variables in their scope.

Let’s understand the code example from the starting of this write up. The return of the Jedi !!

Oh sorry !. It’s just the return of a function. from another function !

When this code is executed, the following happens.

  1. In the global execution environment added to stack, we declared a function makeAdder and a variable addTen.
  2. The value of addTen will be the result returned when we call makeAdder with an argument a=10.
  3. These variables (makeAdder and addTen) are initialized with defaults and secured memory is referenced in the global execution environment during creation phase. Then during the execution phase we encounter the definition and call to makeAdder.
  4. An execution environment for makeAdder is created and the function starts executing with the argument a = 10 and is pushed to stack.
  5. During the execution of makeAdder a variable adder is added to it’s environment and adder function is created along with it’s own execution environment. In that environment scope the argument a to parent makeAdder is saved for reference and we return the created adder function.
  6. The returned function value of adder is saved to variable addTen in global environment along with the associated execution environment created during it’s function definition.
  7. The execution environment of makeAdder is popped from the stack and marked for Garbage Collection.
  8. We then call addTen with the argument b = 5. Variable a is resolved from the captured closure scope of addTen created from the earlier makeAdder lexical environment.

This is how closure functions are able to remember the variables they closed over. By the use of scope chains of linked lexical environments.

We can add debugger statements to the above code and visualize the process in the chrome debugger panel.

Execution Environment of makeAdder function.
Execution Environment of adder function with a closure.

If you’ve worked with React and it’s hooks. The concept of closure and remembering the data is used in the useEffect hook. The function passed to useEffect hook closes over the data (props and state) during a render cycle.

This closure function is remembers the data, and as long as the references in dependency array do not change a new version of the closure is not created and hence it remembers the previously captured data.

Which can cause stale data problem if all dependencies used inside the closure function are not correctly mentioned in the dependency array. To allow creating a new closure with new capture of updated data, mentioning all dependencies is necessary.

Conclusion

So we have learned what functions as first-class citizens, lexical environment, execution context and closures are and how they really work. Closures are fundamental concepts of JavaScript that every JavaScript developer should understand. It helps you to become a much more effective and better JavaScript developer.

References

--

--

Aditya Loshali
The Startup

I’m a full stack web developer working with Javascript on React and Node.js everyday.