Lexical Environment — The hidden part to understand Closures

Amandeep Singh
May 12 · 6 min read

Closures can be a daunting concept when you are new to JavaScript world. Scouring the internet will give you tons of definition about what closure is. But I have felt that mostly these definitions are vague and don’t explain the fundamental cause of their existence.

Today we will try to demystify some of these concepts which are part of the ECMAScript 262 specs, including Execution Context, Lexical Environment, and Identifier Resolution. Additionally, we will learn that because of these mechanisms, all functions in ECMAScript are closures.

I will explain the terminology first and then show you some code examples explaining how all these pieces work together. This will help to solidify your understanding.

Execution Context

JavaScript interpreter creates a new context whenever it’s about to execute a function or script we’ve written. Every script/code starts with an execution context called a global execution context. And every time we call a function, a new execution context is created and is put on top of the execution stack. The same pattern follows when you call the nested function which calls another nested function:

Let’s see what happens when our code is executed as shown in the picture above:

  • A global execution context is created and placed at the bottom of the execution stack.
  • When the bar is invoked, a new bar execution context is created and is put on top of the global execution context.
  • As, bar calls to a nested function foo, a new foo execution context is created and is placed on top of the bar execution context.
  • When foo returns, its context is popped out of the stack and flow returns to the bar context.
  • Once bar execution is finished, the flow returns back to the global context and finally, the stack is emptied.

Execution stack works on a LIFO data structure way. It waits for the topmost execution context to return before executing the context below.

Conceptually, Execution context has a structure which looks like the following:

Don’t worry if structure looks intimidating. We will look at these components shortly. The key point to remember is that every call to execution context has two stages: Creation Stage and Execution Stage. Creation Stage is when the context is created but not invoked yet.

A few things happen in the creation stage:

  • VariableEnvironment component is used for the initial storage for the variables, arguments and function declarations. The var declared variables are initialized with the value of undefined.
  • The value of This is determine.
  • LexicalEnvironment is just the copy of VariableEnvironment at this stage.

Upon execution stage:

  • Values are assigned.
  • LexicalEnvironment is used to resolve the bindings.

Now, let’s try to understand what is a lexical environment.

Lexical Environment

According to ECMAScript specification 262 (8.1):

A Lexical Environment is a specification type used to define the association of Identifiers to specific variables and functions based upon the lexical nesting structure of ECMAScript code

Let’s try to simplify a few things here. A lexical environment consists of two main components: the environment record and a reference to the outer (parent) lexical environment:

Visually it will look like this:

As you can see when trying to resolve the identifier “y” in the foo context, the outer environment (global) is reached out. This process is called identifier resolution and happens on running execution context.

Now, armed with this knowledge of Environments, let’s get back to the structure of Execution context and see what’s happening there:

  • VariableEnvironment: Its environmentRecord is used for the initial storage for the variables, arguments and function declarations, which later is filled on entering the context activation stage.
  • LexicalEnvironment: Initially, it’s just a copy of the VariableEnvironment. On the running context, it is used to determine the binding of an identifier appearing in the context.

Both VE and LE by their nature are lexical environments, i.e both statically(at creation stage) captures the outer bindings for inner functions created in the context. This mechanism gives rise to closures.

Capturing the outer binding statically for inner functions give rise to the formation of closures.

Identifier Resolution aka Scope chain lookup

Before understanding the closure, let’s understand how the scope chain is created in our execution context. As we saw earlier, each execution context has LexicalEnvironment which is used for identifier resolution. All the local bindings for the context are stored in the environment record table. If identifiers are not resolved in the current environmentRecord, the resolution process will continue to the outer (parent) environment record table. This pattern will continue until the identifier is resolved. If not found, a ReferenceError is thrown.

This is very similar to the prototype lookup chain. Now, the key to remember here is that LexicalEnvironment captures the outer binding lexically (statically) on context creation stage and used as it is on the running context (execution stage).

Closures

As we saw in the previous section that upon function creation stage, statically saving of outer binding in the LexicalEnvironment of inner context gives rise to closures regardless of whether a function will be activated later or not. Let see that in an example:

Example 1:

The LexicalEnvironment of foo captures the binding “a” at creation time, which was 10. So, when foo is invoked later (at execution stage), the “a” identifier is resolved with a value of 10 but not 20.

Conceptually, the identifier resolution process will look something like this:

Example 2:

When the outer function returns, its execution context is popped out from the execution stack. But when we invoke the innerFunc() later, it still manages to print out the correct value because LexicalEnvironment of inner function statically captured the “id” binding of its outer (parent) environment when it was created.

Conclusion

  • Execution context stack follows LIFO data structure.
  • There’s one Global context where our code/script is executed.
  • Call to a function creates a new execution context. If it has a nested function call, a new context is created and is put on top of its parent context. When the function finishes executing, it gets popped out of the stack and flow returns back to the context below in the stack.
  • Lexical Environment has two main components: environmentRecord and reference to outer environment.
  • VariableEnvironment and LexicalEnvironment both statically captured the outer binding for inner functions created in the context.
  • All functions at the creation stage statically(lexically) captures the outer binding of their parent environment. This allows the nested function to access the outer binding even if the parent context is wiped out from the execution stack. This mechanism is the foundation of closures in JavaScript.

I hope this article was fun to read and wasn’t overwhelming. Let me know your thoughts in the comments below and if you liked it, a few 👏 will definitely make me smile 😃. Happy coding :)

Amandeep Singh

Written by

Developer @ Thinkmill Sydney. Tech enthusiast and a pragmatic programmer. If coding is hard, you are not doing it right.

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade