JavaScript is a very function-oriented language. As we know, functions are first class objects in JavaScript and can be easily assigned to variables, passed as arguments, returned from another function invocation, or stored into data structures.

A function can access variable outside of it. But what happens when an outer variable changes? Does a function get the most recent value or the one that existed when the function was created? Also, what happens when a function invoked in another place - does it get access to the outer variables of the new place?

Well, let’s dig into this topic to understand these behaviours in JavaScript and understand closures.

Lexical Environment

Variable

Before we start, let’s first discuss what a “variable” actually is. In JavaScript, every running function, code block, and the script as a whole have an associated object known as the Lexical Environment1. Two parts are involved in the Lexical Environment:

  1. Environment Record - an object that has (stores) all local variables as its properties (and some other information like the value of this).
  2. A reference to the outer lexical environment, usually the one associated with the code lexically right outside of it (outside of the current curly brackets).

So, a “variable” is just a property of the special internal object, Environment Record. Working with variables is actually working with the properties of that object.

lexical environment

In the example above, there is only one Lexical Environment (the so-called global Lexical Environment) associated with the whole script, and the rectangle in the centre means Environment Record where stores the variable. The right arrow means the outer reference which points to null (since global Lexical Environment has no outer reference).

Function

Unlike let variables, function declarations are processed not when the execution reaches, but when a Lexical Environment is created. In the global Lexical Environment, it means the moment when the script is started. That’s why we call a function declaration before it is defined.

For instance:

lexical environment with function declaration

Because of the function declaration of say(), the Lexical Environment is non-empty at the beginning. When the execution goes on, it later gets the phrase that declared with let.

Inner and outer Lexical Environment

When a function runs, a new function Lexical Environment is created automatically to store local variables and parameters of the call.

Here’s a picture of Lexical Environment when the execution is inside say("Frank Lin");:

inner lexical environment

The inner Lexical Environment has the outer reference to the outer Lexical Environment.

When code wants to access a variable - it is first searched for in the inner Lexical Environment, then in the outer one, then the more outer one and so on until the end of the chain.2 If a variable is not found anywhere, that’s an error in strict mode.

So, when a function wants to access a variable, it takes the current values from its own or an outer Lexical Environment. In the above example, name takes from its own Lexical Environment in say(); function, and phrase takes from outer (global) Lexical Environment.

Please note that a new function Lexical Environment is created each time a function runs. If a function is called multiple times, then each invocation will have its own Lexical Environment.

Nested functions

In JavaScript, it’s easy to create nested functions that create function(s) inside another. What’s more, a nested function can be returned either as a property of a new object or as a result by itself.

An example with returning a function:

function makeCounter() {
  let count = 0;

  return function() {
    return count++; // has access to the outer counter
  };
}

let counter = makeCounter();

console.log(counter()); // 0
console.log(counter()); // 1
console.log(counter()); // 2

This example creates the counter function that returns the count and increase it by one. But how does the counter work internally?

When the inner function runs, the variable in count++ is searched from inside out:

lexical search order

Here, variable count is found on the 2nd step and returns it. The call to counter() not only returns the value of count, but also increases it. Note that the modification is done “in place”. The value of count is modified exactly in the environment where it was found. So count++ finds the outer variable and increases it in the Lexical Environment where it belongs (the new function Lexical Environment created by makeCounter()).

Each invocation of counter() will increase the count. It may seem unintuitive that this code works in this way. In some programming languages, the local variables within a function exist only for the duration of that function’s execution. Once makeCounter() has finished executing, you might expect that the count variable would no longer be accessible. However, the code still works as expected, this is obviously not the case in JavaScript. We will talk about this in the next section in closure.

Closure

The reason the above example works is that functions in JavaScript form closures that remember outer variables and can access them.

A closure is the combination of a function and the Lexical Environment within which that function was declared.

This Lexical Environment consists of any local variables that were in-scope at the time the closure was created. In this case, counter is a reference to the instance of the inner function count++ created when makeCounter() is run. The instance of count++ maintains a reference to its Lexical Environment, within which the variable count could found from its outer Lexical Environment. For this reason, when counter() is invoked, the variable count remains available for use. Please note how memory management works here. Although makeCounter() call finished some time ago, its Lexical Environment was retained in memory.

In fact, all functions in JavaScript are closures (or perhaps more precisely, all functions are associated with closures). That’s how global variables work, because all functions are closures over the global Lexical Environment.3

Let’s look at another example:

function makeAdder(adder) {
  return function(num) {
    return adder + num;
  };
}

let add5 = makeAdder(5);
let add10 = makeAdder(10);

console.log(add5(2));  // 7
console.log(add10(2)); // 12

In this example, the function makeAdder(adder) takes a single argument, adder, and returns a new function. The function it returns takes a single argument, num, and returns the sum of add and num.

add5 and add10 are both closures. They share the same function body definition, but store different Lexical Environment. In add5’s Lexical Environment, addr is 5, while in the Lexical Environment for add10, addr is 10.

Closure scope chain

For every closure we have three scopes:

  • Local scope (own scope)
  • Outer functions scope
  • Global scope

We have access to all three scopes for a closure. But pay attention to the scopes with nested inner functions:

// global scope
let e = 10;
function sum(a){
  return function sum2(b){
    return function sum3(c){
      // outer functions scope
      return function sum4(d){
        // local scope
        return a + b + c + d + e;
      }
    }
  }
}

var s = sum(1);
var s1 = s(2);
var s2 = s1(3);
var s3 = s2(4);
console.log(s3); // 20

In the example above, we have a series of nested functions, all of which have access to all outer function scopes within which they were declared.

Practical closures

As methods

Closures are useful because they let you associate some data (the Lexical Environment) with a function that operates on that data. This has obvious parallels to object-oriented programming, where objects allow us to associate some data (the object’s properties) with one or more methods. You can use a closure anywhere that you might normally use an object with only a single method.

For instance, suppose we wish to adjust the text size of a webpage, we could use closures like:

function makeSizer(size) {
  return function() {
    document.body.style.fontSize = size + 'px';
  };
}

let size12 = makeSizer(12);
let size14 = makeSizer(14);
let size16 = makeSizer(16);

size12, size14, and size16 are now functions will resize the body text. We can attach them to buttons and then trigger by the user.

Emulating private methods

Languages such as Java provide the ability to declare methods private, meaning that they can only be called by other methods in the same class.

JavaScript does not provide a native way of doing this, but it’s possible to emulate private methods using closures. The following code illustrates how to use closures to define public functions that can access private functions and variables. Using closures in this way is known as the module pattern:

let makeCounter = function() {
  let privateCounter = 0;
  function changeBy(val) {
    privateCounter += val;
  }
  return {
    increment: function() {
      changeBy(1);
    },
    decrement: function() {
      changeBy(-1);
    },
    value: function() {
      return privateCounter;
    }
  }  
};

let counter1 = makeCounter();

console.log(counter1.value()); // 0
counter1.increment();
counter1.increment();
console.log(counter1.value()); // 2
counter1.decrement();
console.log(counter1.value()); // 1

let counter2 = makeCounter();
counter2.decrement();
console.log(counter2.value()); // -1

Here, we create a single Lexical Environment in makeCounter that is shared by three functions: counter.increment, counter.decrement, and counter.value.

The shared Lexical Environment is created in the body of an anonymous function, which is executed as soon as it has been defined. The Lexical Environment contains two private items: a variable called privateCounter and a function called changeBy. Neither of these private items can be accessed directly from outside the anonymous function. Instead, they must be accessed by the three public functions that are returned from the anonymous wrapper. Those three public functions are closures that share the same environment.

Note that each of the two counters, counter1 and counter1, maintains its independence from the other. Each closure references a different version of the privateCounter variable through its own closure.

Code blocks and loops, IIFE

Lexical Environments also exists for code blocks {...}. They are created when a code block runs and contain block-level variables.

if

let phrase = "Hello";
if (true)  {
  let user = "Frank";

  console.log(`${phrase}, ${user}`);
}

console.log(user); // error, can't see such variable!

When the example above execution goes into if block, the new “if-only” Lexical Environment is created. The new Lecial Environment gets the enclosing one as the outer reference, so phrase can be found. But all variables and Function Expressions declared inside if reside in that Lexical Environment and can’t be seen from the outside.

for…while

For a loop, every iteration has a separate Lexical Environment. If a variable is declared in for, then it’s also local to that Lexical Environment:

for (let i = 0; i < 10; i++) {
  // each loop has its own Lexical Environment
  // {i: value}
}

console.log(i); // error, no such variable outside the "for" block

This is actually an exception, because let i is visually outside of {...}. But in fact, each run of the loop has its own Lexical Environment with the current i in it.

Code blocks

We can also use a bare code block {...} to isolate variables into a local scope.

For instance, in a web browser, all scripts share the same global area. So if we create a global variable in one script, it becomes available to others. But that becomes a source of conflicts if two scripts use the same variable name and overwrite each other.

If we’d like to avoid that, we can use a code block to isolate the whole script or a part of it:

{
  // do some job with local variables that should not be seen outside this block

  let message = "Hello";

  console.log(message); // Hello
}

console.log(message); // error: message is not defined

Because the code block has its own Lexical Environment, the code outside of the block doesn’t see variable inside the block.

IIFE

In old scripts, one can find so-called “immediately-invoked function expressions” (abbreviated as IIFE) used for this purpose.

The look like:

(function() {
  let message = "Hello";

  console.log(message); // Hello
})();

Here, a Function Expression is created and immediately called. So the code executes right away and has its own private variables. Note that the Function Expression is wrapped with parenthesis (functiion {...}).

Garbage collection

Lecial Environment objects that we’ve been talking about are subject to the same memory management rules as regular values.

  • Usually, Lecial Environment is cleaned up after the function run. For example:

    function f() {
    let value1 = 123;
    let value2 = 456;
    }
    
    f();
    

    After f() finishes, the Lexical Environment becomes unreachable, so it’s deleted from the memory.

  • …But if there’s a nested function that is still reachable after the end of f(), then the outer Lexical Environment keeps alive as well. When no nested functions remain that reference it, the Lexical Environment object dies.

    function f() {
      let value = 123;
    
      function g() {
        console.log(value);
      }
    
      return g;
    }
    
    let g = f(); // g is still reachable and keeps the outer Lexical Environment in memory
    
    g = null; // now, the memory is cleaned up
    
  • If f() is called many times, and resulting functions are saved, then the corresponding Lexical Environment objects will also be retained in memory.

    function f() {
      let value  = Math.random();
    
      return function() {
        console.log(value);
      }
    }
    
    let arr = [f(), f(), f()];
    // 3 functions in array, each of them links to Lexical Environment from the corresponding f() run
    

Performance considerations

It is unwise to unnecessarily create functions within other functions if closures are not needed for a particular task, as it will negatively affect script performance both in terms of processing speed and memory consumption.

For instance, when creating a new object/class, methods should normally be associated with the object’s prototype rather than defined into the object constructor. The reason is that whenever the constructor is called, the methods would get reassigned (that is, for every object creation). We could avoid using closure as the example below using prototype:

function MyObject(name, message) {
  this.name = name.toString();
  this.message = message.toString();
}

MyObject.prototype.getName = function() {
  return this.name;
};

MyObject.prototype.getMessage = function() {
  return this.message;
};

See more examples from MDN Docs for details.

Also, the JavaScript engines try to optimize the memory of closures. They analyse variable usage and if it’s easy to see that an outer variable is not used - it is removed.

An important side effect in V8 engine is that such variable will become unavailable in debugging:

function f() {
  let value = Math.random();

  function g() {
    debugger; // in console: type console.log( value ); No such variable! The engine optimised it out.
  }

  return g;
}

let g = f();
g();
  1. “Lexical Environment” is a specification object. We can’t get this object in our code and manipulate it directly. 

  2. Clousre, JavaScrip.info 

  3. Is the definition of JavaScript closures on MDN wrong?