“You Don’t Know JS:” My Understanding of Scopes and Closures

Sayan Singha(@dvlpr_ind)
Nov 12 · 10 min read

In the quest to understand JavaScript in as much depth as possible, reading the YDKJS book series by Kyle Simpson is a must. Here’s my understanding of the second book, “Scope & Closures,” in this amazing book series.


“Scope & Closure” by Kyle Simpson

The inclusion of variables in our program is one of the most basic requirements to perform tasks. But it also begets the questions:

1. Where do these variables live, or where are they stored?
2. How does our program find them when it needs them?

The set of well-defined rules that speak on the above questions and more is called scope.


Compiler Theory

Despite the fact JavaScript falls under the general category of a dynamic or interpreted language, it’s, in fact, a compiled language. For simplicity’s sake, I won’t delve into the various steps of compilation.

Any snippet of JavaScript has to be compiled before (usually right before!) it’s executed. So the JS compiler will take the program var a = 2; and compile it first and then be ready to execute it — usually right away.

If we take the example of var a = 2;, the JavaScript engine will break down var a = 2;into separate chunk characters, parse those chunks into what’s called an abstract syntax tree, and then turn that tree into a set of machine instructions to actually create a variable called a if not previously declared in the current scope(in the compilation phase) and later assign it the value of 2 (in the execution phase).


Compiler Lookups

Here the author introduces the concept of left-hand side (LHS) and right-hand side (RHS) lookups. But the question is: to the side of what? To the side of an assignment operation.

The LHS lookup is done when a variable appears on the left-hand side of the assignment operation, as in the var a = 2; example, where the LHS lookup is trying to find the variable container itself so it can assign.

The RHS lookup is done when a variable is on the right-hand side of an assignment operation, as in console.log(a);. The reference to a is an RHS reference because nothing is being assigned to a here. Instead, we’re looking up to retrieve the value of a so the value can be passed to console.log(…). By contrast, in a = 2;, the reference to a is an LHS reference because we don’t actually care what the current value is — we simply want to find the variable as a target for the =2 assignment operation.

If the variable can’t be found in the scope chain for an LHS lookup, a global variable will automatically be created (when in nonstrict mode), or a ReferenceError will be thrown (when in strict mode).

If the variable can’t be found in the scope chain for an RHS lookup, a ReferenceError will be thrown.

So whenever you see a ReferenceError in the console, you can be sure it relates to a scope failure.


Lexical Scopes

Lexical scope is a scope that is defined by the JavaScript author at the time of writing the code. (This is a nice way of thinking about scope!) It all comes down to where the author chooses to place his/her variables and functions (and blocks). This placement is what tells the JavaScript engine where it should perform its LHS and RHS lookups — in other words, the chain of scopes it needs to move up through to find a variable.

JavaScript uses the lexical scope

It gets easier and more interesting to understand and code when we approach the concept of nested scopes by looking at them from the point of view of RHS and LHS.

Consider:

The RHS reference for b can’t be resolved inside the function foo, but it can be resolved in the scope surrounding it (in this case, it’s the global scope).

The simple rules for traversing nested scope

We start at the currently executing scope and look for the variable there. Then if not found, we keep going up one level — and so on. If the outermost global scope is reached, the search stops, whether it finds the variable or not.

The book gives a really great example of nested scopes as “bubbles inside of each other.”


Cheating Lexical

Before I discuss the two cheats or ways to modify the lexical scope, I must give a warning — for mainly two reasons:

  1. It’s a bad practice and is frowned upon by the wider JavaScript community
  2. It leads to poorer performance

And, therefore, I’ll mention them in not much detail. Nevertheless, it’s interesting. OK, here we go:

eval

Consider this:

Quite simply, you can see that the eval(..) takes a string and treats the contents of the string as if it had actually been authored code at that point in the program.

In other words, you can programmatically generate code inside your authored code and run the generated code as if it had been there at author time.

with

with is typically explained as a shorthand for making multiple property references against an object without repeating the object reference itself each time.


Hiding Stuff in Functions

Here we introduce the principle of least exposure. As the name suggests, we should expose only what is minimally necessary and, essentially, hide everything else.

Collision avoidance

Another benefit of hiding variables and functions inside a scope is to avoid unintended collisions between two different identifiers with the same name but different intended usages. Collisions often result in the unexpected overwriting of values.

For example:

The i = 3 assignment inside of bar(..) overwrites, unexpectedly, the i that was declared in foo(..) at the for-loop.

In this case, it’ll result in an infinite loop, because i is set to a fixed value of 3, and that’ll forever remain < 10.


Function Declaration vs. Function Expression

This is fairly a very important concept I was completely oblivious to.

Simply put, if function is the first thing in the statement, then it’s a function declaration. Otherwise, it’s a function expression.

Function declaration:

Consider this:

The (function foo(){ .. }) as an expression means the identifier foo is found only in the scope where the .. indicates, not in the outer scope. Hence, the values inside the scope that encloses (function foo(){ .. }) are not polluted by what’s in the global scope.

The following are the different ways of writing a function expressions:


Immediately Invoked Function Expression (IIFE)

IIFE can be written in two different ways:


Blocks As Scopes

Consider a for loop:

Now, we can clearly see that it makes a kind of sense that we declare the i inside the for loop, but it’s not relevant when using var because they’ll always belong to the enclosing scope. Then, why pollute the entire scope of a function with the i variable that’s only going to be used in the for loop? The solution to that is let and const. Let’s take a look at them:

Let and const create a block scope of their own, as does the catch in a try…catch statement.

The following examples will clear it further:

try…catch

As you can see, err exists only in the catch clause and throws an error when you try to reference it elsewhere.

let

The same code when run with var:

const

Const also creates a block-scoped variable, but the value is fixed (constant). Any attempt to change that value at a later time results in an error.


Hoisting

This is, perhaps, the most important conclusion of everything we’ve learned above — and maybe the most interesting too.

Let's look at two scenarios:

Now the reason why the outputs might seem confusing is because of hoisting.

The JS engine works in two steps: The first is the compilation phase, and the next is the execution phase. The work of these two steps is different.

In the compilation phase, all variables and function declarations are processed.

After the compilation phase, in the execution phase, all the logic and assignments take place.

When you see var a = 2;, you probably think of that as one statement. But JavaScript actually thinks of it as two statements: var a; and a = 2;. The first statement, the declaration, is processed during the compilation phase. The second statement, the assignment, is left in place for the execution phase.

Note: In the above example, the in place part is very important. It’s also important to note that hoisting is per scope.

Our first snippet will then be:

And our second snippet:

Functions first

A subtle yet important detail: Functions are hoisted first — and then variables.

Consider:

1 is printed instead of 2. This snippet is interpreted by the engine as:

While multiple/duplicate var declarations are effectively ignored, subsequent function declarations do override previous ones.

Consider the next two snippets:

Snippet 1:

Snippet 2:

Function declarations that appear inside of normal blocks typically hoist to the enclosing scope, rather than being conditional, as the above code implies.


Closure

Closure is a fairly simple concept to understand if one understands lexical scope well enough. Essentially, we use closure quite often but without really understanding it.

We can look at closure as a way of calling a function or using a function out of its lexical scope or the ability of the function to access its lexical scope even when that function is called outside its lexical scope.

Consider this simple code:

To make the above code even clearer, consider this:

So to define it:

Closure is when a function is able to remember and access its lexical scope — even when that function is executing outside its lexical scope.


Modules

I was not familiar with the idea of modules as explained in this book, but they’re very interesting and helpful.

Modules are code patterns that leverage the power of closure.

Take this for an example:

This is the pattern in JavaScript we call module.

To state it more simply, there are two requirements for the module pattern to be exercised:

  1. There must be an outer-enclosing function, and it must be invoked at least once (in the above example it is Module).
  2. The outer-enclosing function must return back at least one inner function so this inner function has closure over the private scope and can access and/or modify that private state. (In the above example, it is func.)

Done

It’s now time to delve into the third book on this and object prototypes

Happy coding!

Better Programming

Advice for programmers.

Sayan Singha(@dvlpr_ind)

Written by

Computer Science Engineer from Kolkata. All things JS. Twitter: @dvlpr_ind

Better Programming

Advice for programmers.

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