JavaScript : Closures & Environment

Regardless of your experience level with JavaScript, sooner or later you will encounter closures. I vividly remember the day I wrapped my head around closures. It opened me up to a ton of concepts like partial application, currying etc.

In this blog post, I am going to jot down my understanding of the concept in simple terms.

What is a closure?

A closure is an inner function that has access to an outer function’s variables. To create a closure, just define a function inside another function & expose it by either returning it or passing it to another function. A simple example of a closure is our add function below

function add(x) {  
    return function(y){
        return x + y;
    }
}

const addFive = add(5);  

When we call the function add, it returns the inner anonymous function & assigns it to the variable named addFive. This inner function has a reference to the variable x from the outer function even though the outer function has already returned. For sanity check let’s log addFive to the console.

Now let’s try calling the addFive function to see if it actually has access to the value assigned to x in the outer function.

console.log(addFive(10)); //15  

Voila!

That’s it. That’s what closures are in a nutshell. But wait a minute, how does it work? How does the inner function know about the variables from the outer function even after the outer function has already returned?

Environment

To answer that question we must familiarize ourselves with another concept called "environment". Every time a function is invoked, a new environment is created. An environment is a dictionary that maps variables to values by name. Let's take a look at an example:

function a (x) { return x });  
a(5);  

When we call function a, a new environment is created. Inside that environment, the value 5 is bound to the name x. Therefore, the environment looks something like this

{ x : 5 }

Closures & environment

Now that we know how environments are created when a function is called, we can use that knowledge to understand how closures work under the hood.

function outer(x) {  
  const z = 4;
  return function inner(y){
     return x + y + z;
  }
}

outer(1)(2); //returns 7  

When we call the outer function & pass in the value 1 as a parameter, it is bound to the name x in the environment. Inside the function, we define another variable & bind it to the name z in the environment. Therefore, the environment belonging to the outer function becomes { x : 1, z : 4 }.

When we call the returned inner function & pass in the value 2 , the environment belonging to the inner function becomes { y : 2 }

But how are the expressions x & y going to be evaluated in the inner function? There is no x in the inner function's environment. Every function's environment has a reference to its parent's environment. Therefore, the environment of inner function actually looks like this.

{ y : 2, '..' : { x : 1, z : 4 } }

In the example above, .. represents the "parent environment". JavaScript always searches for a binding starting with the function's own environment and then traverses each parent environment until it finds a binding by that name or it reaches the outer most environment. Let's look at another example:

function outer(x){  
  return function inner(x) { 
    return x;
  } 
}

In this scenario, the x in the outer function's environment is ignored because another binding with the same name exists in the inner function's environment.

What if there is no outer function? What happens then?

JavaScript always has one global environment in which many helpful things are bound such as libraries full of standard functions. This global environment is referred to as window in the browser & global in node.

Other than the named arguments & variables defined in your function, Javascript also binds a few "special" names in the environment.

  • this - bound to the function's context.
  • arguments - contains a list of all arguments passed to the function.

There you have it. That is my understanding of how closures work under the hood. Hope you found this helpful :)