DEV Community

loading...
Cover image for Closures in JS and Why It Matters

Closures in JS and Why It Matters

Ilê Caian
I like dogs and music. Eventually I code. 🐶woof!
・4 min read

One of the main features when a developer writes JavaScript code is maybe the one most unknown by them. Maybe this happens just because no one writes code thinking directly or even knowing that the reason their code doesn't break is related to this feature.

But what is that feature?

Well... it is not a feature actually. It is a side-effect of how JavaScript is built and how it 'compile', run and executes. Let's dig in with an example.

Running the following in the browser dev tools will result in

var age = 14;

function getOlder() {
  var age = 14;
  age++;
};

getOlder();

console.log(`I am ${age} years old.`); // <-- ???
Enter fullscreen mode Exit fullscreen mode
  1. It breaks (🤷)
  2. Print I am 14 years old.
  3. Print I am 15 years old.

The correct answer is 2: I am 14 years old.! But why?

Explaining the Execution

There is a lot of important information about AST (Abstract Syntax Tree) and how JS was conceived that will not be the case of study here but for the reader (check the References!), think like this:

When the Virtual Machine that runs inside your Browser (V8 in Chrome for example) executes the code it makes the naming resolution of each variable. This process of resolving the variables is required so while using a variable that is declared and defined it doesn't break your code. If the code tries to access some function or variable that is not properly defined yet it will output the famous:

Uncaught ReferenceError: yourVariable is not defined.

Photo by Mark de Rooij on Unsplash

Photo by Mark de Rooij on Unsplash

Resolving Variables by Hand

If the result after naming resolution is accessible, the original code will be converted to something roughly similar to:

var global__age = 14;

function global__getOlder() {
  var getOlder__age = 14;
  getOlder__age++;
};

global__getOlder();

console.log(`I am ${global_age} years old.`); // --> 'I am 14 years old.'
Enter fullscreen mode Exit fullscreen mode

Now it makes sense that the output is I am 14 years old., right? This prefix added is related to the Closure of each variable and method when the naming resolution happens. As can be observed, there are 2 Closures in this code:

  • global
  • getOlder

It can be noticed that the getOlder Closure is inside the global Closure but the variables inside the getOlder() original function are only accessible inside those brackets.

So, it makes much more sense saying that the getOlder__age variable only exists inside the global__getOlder() function. A good example to validate is trying to log the variable from inside the function, outside of it:

var global__age = 14;

function global__getOlder() {
  var getOlder__age = 14;
  getOlder__age++;
};

global__getOlder();

console.log(`I am ${getOlder__age} years old.`); // --> Error!
Enter fullscreen mode Exit fullscreen mode

The resulted output is Uncaught ReferenceError: getOlder__age is not defined and the reason is that there is no variable with the naming resolved to global Closure valid for getOlder__age.

But what about the Scopes?

In the creation of a function, a Closure is created the same way as a Scope. All variables and functions inside that both are accessible to all child functions and not outside of it (except if they are exposed like it'll be discussed ahead).

Scope and Closure are almost equal but the second one has some 'super-powers': Variables and functions created inside the Closure and exposed will still work outside of it, even without the existence of Scope. This is a very tight line between those two concepts.

This is true even if those exposed items depends on other variables/functions inside the Closure but are not exposed.

Closures vs. Scopes

Using almost the same example as above with little changes in order to explain differences between these two concepts, the following code is a starting point

function main() {
  var age = 14;

  function getOlder() {
    age++;

    console.log(`I am ${age} years old now.`); // --> 'I am 15 years old.'
  };

  getOlder();
};

main();
Enter fullscreen mode Exit fullscreen mode

With this example, the function getOlder() will be called inside the main() function and it'll print I am 15 years old now., correct? The variable age is inside the main scope and can be accessed by getOlder() function.

Returning the getOlder() function to the outside 'world' and executing it 3 times as the following example, what will be the result?

function main() {
  var age = 14;

  function getOlder() {
    age++;

    console.log(`I am ${age} years old now.`); // <-- ???
  };

  return getOlder;
};

var getOlder = main();

getOlder(); // <-- ???
getOlder(); // <-- ???
getOlder(); // <-- ???
Enter fullscreen mode Exit fullscreen mode
  1. Nothing. The code will break.
  2. 3 times I am 15 years old now.
  3. The value of the age variable will still increase from 15, to 16, and then to 17.

The correct answer is answer 3.

But why this happens?

Every time a Closure is created, all the variables and functions are stored inside its state. Even after the end of execution of the main() function, the respective Closure state is still alive storing variables and functions!

Maybe the most awesome part of it is: the age variable is lost inside that main() Closure and is not accessible outside of it! If the next part of the code tries accessing that age variable, it'll result in the already discussed Uncaught ReferenceError: age is not defined error, as this variable doesn't exist outside the main() function!

Photo by Maria Teneva on Unsplash

Photo by Maria Teneva on Unsplash

Wrap up

Some awesome differences between Closure and Scope concepts were discussed:

  • Closures always store state about its variables and functions
  • It is possible to expose some, all or none of those variables/functions by returning them at the end of the function that creates the Closure
  • It is possible to even redefine some outside variables/functions inside the Closure with the same name and the Virtual Machine compiler will take care of it, avoiding errors in runtime and name collisions

Is this article useful for you? Did I miss something while explaining? Please, let me know in the comment section or send me a message!

References

Discussion (3)

Collapse
karranb profile image
Karran Besen

Awesome post !

Collapse
caiangums profile image
Ilê Caian Author

Thanks! 🚀

Collapse
willkraemer profile image
William Kraemer Aliaga

Your article brought me some insights I haven't had at first when learning about closures. Thanks!