DEV Community

Cover image for CLOSURES!!
Mohamed Yahia
Mohamed Yahia

Posted on

CLOSURES!!

A closure is a function with its outer function lexical scope/environment.

What does 'lexical' mean?

  • 'Lexical' means relating to words, so the 'lexical scope of a function' is the scope related to the wording of that function or the definition of that function.
function doSth() {
  //some code
  function doSthFancy() {
    // do sth fancy
  }
}

function doSthElse() {
  doSth();
}

doSthElse();
Enter fullscreen mode Exit fullscreen mode

The 'lexical scope' of the function above doSth is the global context since it is worded/defined in the global space, but the invocation scope is the doSthElse function space since it runs there.

Now back to the closure definition.

A closure is a function lexical scope with its outer function lexical scope/environment.

So, what is the closure of doSth? its doSth + global scope (which contains doSthElse as a variable).

Why we were able to run doSth from doSthElse? Because doSth is in the lexical scope of doSthElse (lexical scope of doSthElse is the global scope and doSth is defined there so it is available for doSthElse to use).

BUT if we added doSthFancy,

function doSthElse() {
  doSth();
  doSthFancy();
}
Enter fullscreen mode Exit fullscreen mode

and tried to execute doSthElse, it would throw an error complaining that doSthFancy is not defined.

So, why is a closure useful? JUST A DEFINITION!?

Let's see.

First: (which is the boring usecase)

function outerFunction() {
  let variable = "some variable";

  function innerFunction() {
    console.log(variable);
  }
  innerFunction();
}

outerFunction(); // 'some variable'
Enter fullscreen mode Exit fullscreen mode

If we ran outerFunction, 'some variable' is console logged but why? There is no variable variable declared in the innerFunction, how did innerFunction get the value of variable from the outerFunction? That is because functions have access to variables declared within its lexical scope which includes the outerFunction.

SO BORING YES, but watch this:

function outerFunction() {
  let variable = "some variable";

  function innerFunction() {
    console.log(variable);
  }

  return innerFunction;
}

const someFunction = outerFunction();

someFunction();
Enter fullscreen mode Exit fullscreen mode

NOW, in the assignment of someFunction to the outerFunction call, the innerFunction is returned AND it didnt run. Outer function got executed, finished executing, no longer there in the call stack, variable value is not there anymore sinced its gone.

BUT then we run the innerFunction at someFunction() where the invokation context now is the global space and there is no variable variable there so the console log value should be undefined BUT THAT IS NOT THE CASE.

The logged value is 'some variable' MAGIC? No, its closures baby. That function someFunction gets the variables defined in its lexical scope when it is invoked to work with.

Image description

You might be thinking now, that's interesting. You know what is more interesting? You. Nah, I am joking. You are not that interesting or you are but you know what's more interesting? You're not falling for it again, huh. Ok, what's more interesting is that someFunction doesnt only get its lexical scope but also its parent/outer function lexical scope, watch this.

function outerOuter() {
  function something() {
    console.log("something");
  }

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

  return outer;
}

const outer = outerOuter();
const inner = outer();

inner(); //something
Enter fullscreen mode Exit fullscreen mode

The lexical scope of the parent function is this

Image description

and that's why 'something' was logged

Practical Example for closures

Lets say we have some buttons and we want to run event handlers when they are clicked

button.addEventListener("click", () => {
  document.body.appendChild("div");
});
button2.addEventListener("click", () => {
  document.body.appendChild("p");
});
button3.addEventListener("click", () => {
  document.body.appendChild("a");
});
Enter fullscreen mode Exit fullscreen mode

document.body.appendChild is repeated in these 3 handlers, I dont like that, the only thing changing is the element name. so what can we do?

Well the first guess is that we would make a function that accepts that dynamic/changing part as a parameter and it would return that repeated logic with the element name that it got passed

function appendEl(el) {
  document.body.appendChild(el);
}
Enter fullscreen mode Exit fullscreen mode

Spectactular. Now we only have to pass that function as the event handler of the click event

button.addEventListener("click", appendEl(el));
Enter fullscreen mode Exit fullscreen mode

Is that right? Nope, because we are not passing a function, we are invoking it and what we are actually passing is the evaluation of the appendEl call which is the return value of it which is undefined. so how could we solve this?

an easy way would be this

button.addEventListener("click", () => {
  appendEl("div");
});
Enter fullscreen mode Exit fullscreen mode

To just add it as the function body of another function

Another solution would be to use bind (and it is more useful in some cases where we need to assign the button object to this)

button.addEventListener("click", appendEl("div").bind());
Enter fullscreen mode Exit fullscreen mode

Third solution which I consider the cleanist is by making appendEl return a function containing the logic.

** ALERT: HIGHER ORDER FUNCTION INCOMING **

function appendEl(el) {
  return function () {
    document.body.appendChild(el);
  };
}

button.addEventListener("click", appendEl("div"));
Enter fullscreen mode Exit fullscreen mode

Then we could just do this. Now appendEl will return a function and when it runs it will need the el variable which will be present for the inner function to use even though appendEl ran and finished execution and that was made possible by CLOSURES.


That is the CLOSURE of this article (see what I did there!), hope you enjoyed it, understood it, and sEe yOu iN tHe nExT oNe.

Top comments (0)