Daniel Escobedo

Lexical Scope and Closures

Introduction
Lexical Scope and Closures

The scope model that JavaScript employs is called Lexical Scope. So what is it exactly?

A scope acts like a container in which your variables and functions are declared.

var name = 'Daniel';

function printName() {
  console.log(name);
};

printName(); // Daniel

There are two scopes in this example. The global scope, which encapsulates everything, and the scope of the function printName.

Lexical Scope is a set of look-up rules that use the location where your variable is declared to determine where that variable is available. Functions have access to variables declared in their outer scope. In this case, the function printName has access to the variable name located in its outer scope.

One way to think about lexical scope is that inner containers always have access to variables and functions in their outer containers.

Technically, the outer scope should not have access to variables and functions contained in the inner scope. At JavaScript's inception, however, this was only possible with Functions.

Function Scope

JavaScript initially only had function-based scope (until try/catch in ES3, and now let/const binding in ES6). This meant that this was the only way to hide your variables and functions.

Why is scope-based hiding important?

One reason comes from a software design principle called the "Principle of Least Privilege". Following this principle in JavaScript would mean you keep all your variables and functions available only to your immediate scope. Essentially you are hiding them from the outer scopes.

This is also helpful so you avoid unintended collision between two different identifiers with the same name. Depending on your application, identifier names can sometimes be used multiple times in different scopes for different reasons. Not scope-hiding can result in overwriting your own variables and produce in unexpected results. This is why libraries usually do have a specific global namespace (think jQuery, $) so that their variables and functions don't collide with other libraries.

Block Scope

For some time, many languages other that JavaScript supported block scope.

for (var i=0; i<10; i++) {
  console.log(i);
}

This for loop is written this way because we only intend to use our variable i within this scope. There's one problem with this assumption. Variables declared with var do not have block scope; In other words, block statements do not introduce a scope. i is actually scoping itself to the enclosing scope (the global scope in this case).

This means that it doesn't matter where we declare our variables when we use var. They automatically belong to the outer scope.

var dataReceived = true;

if (dataReceived) {
  var data = getData();
  data = processData(data);
  console.log(data);
}

In this example, while I only intend to use my data variable within the scope of this if statement, it is actually available to its outer global scope. This is pretty much a fake scope I created for visual purposes. I rely on myself not to accidentally reuse data outside of this if scope.

There is another problem with my example. My global scope doesn't need data at all. If this variable was memory-heavy, now it can't be garbage collected after the if statement finishes with it. Because of lexical scope rules, this variable will now be kept around in my application even though we do not need it anymore.

So how do we fix this issue?

let and const

JavaScript now supports block scope thanks to ES6. Identifiers declared with let or const do have block scope.

These keywords attach their variable declaration to the scope of whatever block they're contained in. Looking back at our previous example we can simply swap out var with let (assuming environment supports ES6) to improve performance. Now our data variable gets garbage collected after it exits the if statement freeing up our memory!

Note: There are also other things that happen when you use let and const. One immediate thing to note is that while let and const do hoist up, you cannot access them before the actual declaration is evaluated at runtime. Swapping out var with let can potentially break your application if you are not careful.

Scope Closure

When we write code that relies on lexical scope, we usually write closures.

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

function concatFamilyName(lastName) {
  function printName(firstName) {
    console.log(firstName + ' ' + lastName);
  }
  
  return printName;
};

var printFullName = concatFamilyName('Escobedo');

printFullName('Daniel'); // Daniel Escobedo

So what's happening?

The function concatFamilyName returns a reference to its inside function printName. The function printName has lexical scope access to its immediate outer scope. This means the variable lastName (provided as a parameter) is accessible inside of printName.

Now we execute concatFamilyName and define the lastName argument to be my last name. Then we assign the value it returns to printFullName, which is a reference to printName.

Finally we call printFullName, which is actually invoking our inner function printName.

Did you see what happened? We invoked the inner function printName outside of its declared lexical scope, and it still remembered the value of lastName. So even though we exited the function concatFamilyName, printName still kept its scope around so that it could use it later.

This is closure. It is said that the function printName has a closure over the scope of concatFamilyName.

Another way to think about it is that inner functions contain and keep the scope of their parent function even if the parent function has returned.

Closures may seem like a foreign concept to many, but understanding them can really help you visualize and see them happening all the time.

Author

Daniel Escobedo

I'm a JavaScript developer who loves to code. I'm always reading and learning new technologies and tools to better my skills and workflow.

View Comments
Next Post

JavaScript for...in vs for...of Loops

Previous Post

JSON Web Token Structure

Mastodon