Skip to content

Instantly share code, notes, and snippets.

@Kotauror
Last active April 15, 2020 10:09
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Kotauror/3bad870bf4e813766b4f3491c1bda034 to your computer and use it in GitHub Desktop.
Save Kotauror/3bad870bf4e813766b4f3491c1bda034 to your computer and use it in GitHub Desktop.
Closure in Javascript

Closure in JavaScript

With every fancy framework and even fancier plugin to it, JavaScript is getting easier to use - more things happening behing the scenes, less difficult algorithms and procedures to write by hand. However, every now and then in the troublefree world of frameworks and libraries one might encounter an unexpected problem and then the JavaScript rules will creep out of the shadows and there will be no other other option but to finally face them.

In this entry, I'll go back to JavaScript basics to explain one of the fundamental concepts - a closure. I'll start with explaining the documention on very simple examples and then I'll move on to two more practical use cases.

Cracking the documentation code

Let's start talking about the closure from looking at the definition from documentation:

A closure is the combination of a function bundled together (enclosed) with references to its surrounding state (the lexical environment). In other words, a closure gives you access to an outer function’s scope from an inner function. In JavaScript, closures are created every time a function is created, at function creation time.

That was hard to read. Let's analyse it line by line starting from the first sentence:

A closure is the combination of a function bundled together (enclosed) with references to its surrounding state (the lexical environment).

A lexical environment that the definition refers to is a JS engine construct that is used to manage data in a nested blocks of code. JavaScript has two main types of contexts - global and local. The global context is the most outer layer - eg. the <script> </script> tag that wraps the entirety of the code. The local contexts are the inner layer - they are created each time we create a new block of code with {...} (see more about it here). Anything declared outside of a function or an object belongs to a global context, while the rest might belong to one or many local contexts.

Let's take a look at this simple example:

let x = 1;

function func() {
  let y = 2;
}

Here the variable x belongs to the global context, while y to the local context of func.

A more complex example:

function outerFunc() {
  let x = 1;

  function firstInner() {
    let y = 2;
    console.log(x + y); // 3
  }

  function secondInner() {
    let z = 3;
    console.log(x + z); // 4
  }
}

In this case, outerFunc() creates a variable x and two functions firstInner and secondInner. We have three lexical contexts here - the outer context of outerFunc and two inner contexts of firstInner and secondInner that share the same outer environment. Since firstInner and secondInner can access the variable of the outer context in which they were created, they are able to read the value of x.

Understanding the environments moves us a step closer to understanding the first part of the definition; the lexical environment determines a scope which can be accessed from a function to find references that this function needs. The search will start in the direct scope and more towards the parent scopes if the reference is not found.

If we are not sure of the scope available to a function, we can use console.dir() and Chrome dev tools to check it. Let's go back to a simpler example of not nested function and try it out: a function (adding) is referencing a variable (number) from the global context.

  let number = 2;
  let adding = function() {
    let inner = 1;
    return number + inner;
  };

  console.log(adding()); // 3
  console.dir(adding)  // <------ logging the scope

When we check the console, we'll be able to see the [[Scopes]] array. It will only have the Global scope, which is "one level up" from the function and is the only place that can be reached in order to determine the value of number.

Zrzut ekranu 2020-04-14 o 13 38 09

You might now wonder, shouldn't we be dealing with a closure here? The function adding is reaching to its surrounding state, so it this closure already? The answer is no and it's because using a variable from the global context does not yet constitute a closure. The second sentence of the definition explains that a closure requires reaching from an inner function to an outer function, but no to the global context:

In other words, a closure gives you access to an outer function’s scope from an inner function.

Only referencing a variable from the outer function (ie. not from a global context) can be understood as a closure. So let's add an outer function to the example that will wrap adding.

function calculations() {
  let number = 2;
  let adding = function() {
    let inner = 1;
    return number + inner;
  };

  console.dir(adding)
}
calculations()

When we check the Chrome dev tools again, we will see that the [[Scopes]] array now includes an additional scope - Closure - with the outer function (calculations) and the variable number!

closuree

Here we are, our closure! 🎉 That's quite cool!

Let's try out a more complicated example. This time we'll pass the variables as arguments instead of defining them in functions like in the example above. We will also return the inner function insted of returning only the result.

function calculations(x) {
  return function(y) {
    return x + y;
  };
}
let add5 = calculations(5);
let add10 = calculations(10);

console.log(add5(2));  // 7
console.log(add10(3)); // 13

First, we create a calculations function that accepts an x argument. The x argument will be available in the scope of this function, what means that it will be available to the inner annonymous function created inside of it. calculations returns an annonymous function that accepts an y argument and returns x + y.

In order to get the result, we first call makeAdder with the argument x.

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

makeAdder serves as a function factory and returns us the two inner functions: add5 and add10. They share the same function definition, but different lexical environemnts, as the arguments that were passed to them are different. As mentioned above, both function will have access to x as they were created in a scope where x was available.

Then we call the inner functions that were returned from makeAdder and pass the y argument to them.

console.log(add5(2));  // 7
console.log(add10(3)); // 13

And it works! JS closures allowed us to: 1) keep the inner context of the makeAdder "hidden" (calling the inner funciton directly is impossible), 2) reuse the funciton definition while maintaining different lexical environements!

Now that we know a few things about the scope and closure, it's timee for the last sentence of the definition:

In JavaScript, closures are created every time a function is created, at function creation time.

This sentence, comparing to the rest of the definition, sounds straight forward and looks innocent, but don't skim over it just because it looks simple.

The consequence of this sentence is that a closure will be created with the value of the variable from the moment of its creation. The following updates to the variable won't necessarily affect the enclosed function. I'll explain it in more detail in the first practical example below.

Practical examples

Now that we have a taste of what a closure is, let's move on to more practical examples. I'll discuss two potential use cases: closure in intervals and closure as a way to emulate a private scope.

Closure in intervals

Investigating closures in intervals is a fine way to understand it! In order to present it, I've prepared a Closures component that shows how many seconds have elapsed. I'll use functional components and hooks in order to keep the code very short and TypeScript for some increased visibility.

import React, { FunctionComponent, useState, useEffect } from "react";

const Closures: FunctionComponent<{}> = () => {
  const [seconds, setSeconds] = useState<number>(0);

  useEffect(() => {
    setInterval(() => {
      setSeconds(seconds => seconds + 1);
    }, 1000);
  }, []);

  return (
    <div>
        {seconds} seconds have elapsed since mounting.
    </div>
  );
};

export default Closures;

I set the intervals at mount with useEffect (in class component it would be componentDidMount). The interval will call the setSeconds every second; the component will re-render each time the state changes.

ezgif com-video-to-gif (16)

Works as expected, nothing unusual to see here.

What is truly fascinating though is how the values are changing under the hood. We might expect that if we console.log(seconds) within the interval, it will be changing every second, however - that's not what is happening!

...
  useEffect(() => {
    setInterval(() => {
      console.log(seconds) // <-------- add the console.log()
      setSeconds(seconds => seconds + 1);
    }, 1000);
  }, []);
  ...

ezgif com-video-to-gif (17)

The seconds within the interval remained at the initial value of seconds that was set on mounting with useState(0) and remained the same!

If we wanted to successfully console.log the seconds in the interval, we'd have to use a reference to an object, not a primitive value. It's because the closure keeps the reference to the object, it doesn't care about the values of its fields.

We can achieve it by using a ref for the seconds - I'll do it by using the useRef hook. I first create a reference (const secondsRef = useRef(seconds);) and then assign the current value of ref to the seconds (secondsRef.current = seconds;). The closure won't "remember" the current value, only the ref object, so logging it in interval will give us the desired outcome.

...
const Closures: FunctionComponent<{}> = () => {
  const [seconds, setSeconds] = useState<number>(0);

  const secondsRef = useRef(seconds); // <---------
  secondsRef.current = seconds; // <---------
  useEffect(() => {
    setInterval(() => {
      console.log(secondsRef.current) // <---------
      setSeconds(seconds => seconds + 1);
    }, 1000);
  }, []);
  ...
};
...

ezgif com-video-to-gif (18)

Of course, logging the values to the console is not the reason we all need to understand the closure. It's quite easy to think of a more practical implementation within the current example - an if statement inside the interval. Let's say that we'd like to trigger an additional action after the counter reached 5 seconds - without using the ref, we'd be stuck with seconds that equal 0.

const Closures: FunctionComponent<{}> = () => {
  const [seconds, setSeconds] = useState(0);
  const [showAdditionalInfo, setShowAdditionalInfo] = useState(false); // <--- add a new state

  const secondsRef = useRef(seconds);
  secondsRef.current = seconds;
  useEffect(() => {
    setInterval(() => {
      console.log(secondsRef.current)
      setSeconds(seconds => seconds + 1);
      if (secondsRef.current === 5) { // <----- using (seconds === 5) wouldn't work! 
        setShowAdditionalInfo(true)   // <-----
      }                               // <-----
    }, 1000);
    }, []);

  return (
    <div>
        {seconds} seconds have elapsed since mounting.
        {showAdditionalInfo && <p>I'm the additional info</p>} // <----- use the new state
    </div>
  );
};

ezgif com-video-to-gif (19)

Using closure to emulate a private scope.

Unline many other languages, JavaScript doesn't have a native solution for private elements. However, one can achieve a similar result by using a closure!

The code below defines a bankAccount function that has a balance variable and a changeBy function defined inside of it. The changeBy function uses the balance even though it's not defined in it - it can reach for it to the environemnt where it was created in, where the balance is also available (closure).

The bankAccount returns (or one might say - exposes) - an object which has three functions on it - increment, decrement and getBalance.

We can create a new instance of a bank account by running var myAccount = bankAccount().

function bankAccount() {
  let balance = 0;
  function changeBy(val) {
    balance += val;
  }
  return {
    increment: function() {
      changeBy(1);
    },
    decrement: function() {
      changeBy(-1);
    },
    getBalance: function() {
      return balance;
    }
  };   
};

let myAccount = bankAccount()

Designing our code this way makes it impossible to access the balance variable and the changeBy function directly, as they are not available (exposed) outside of bankAccount. We can only access them by calling the functions on the returned object:

console.log(myAccount.getBalance()); // 0
myAccount.increment(); 
console.log(myAccount.getBalance()); // 1
myAccount.decrement();
console.log(myAccount.getBalance()); // 0

This way we're able to emulate the private scope and make it impossible to directly reach certain areas of code.

Good to be back

To close this blog entry I'd like to say that it's good to be back to basics! When juggling multiple libraries on a daily basis and trying to catch up with all the hot and fancy stuff that appears each week, it's easy to get a bit rusty when it comes to the fundamental skills.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment