Scopes and Closures

Posted on February 15, 2019

This is part of a series where I try to explain through each of 33 JS Concepts.

This part corresponds to the Scopes and Closures.

Scope

What is scope of a variable?

Scope in programming stands for visibility and usage. For reasons considered obvious (but ones that we will still discuss), we cannot allow all variables in our program to be global. Scope is what limits the variable to used across certain limits and boundaries.

Why scope variables?

1. Collision Avoidance

We cannot declare two variables with the same name. This would throw a reference error.

1var length = 1;
2// some operation going on, after sometime you think
3var length = 1; // Nope!`}

But we absolutely cannot not use the same variables for different purposes. If there are no boundaries, it becomes confusing for people who read your code. This becomes more problematic when there are lots of people on the team. How does anyone know if someone else has already declared the variable?

Clear boundaries on where you can access a variable makes it easier for all developers in the project to avoid conflcits.

2. Garbage Collection

When we complete the usage of a variable, we want the variable and the data it is holding to be garbage collected. In dynamic languages, we expect this to happen automatically. But if we don’t have any boundaries on where the variable can be accessed it would happen that the compiler does not have any hint on when to collect the garbage. Except may be at the end.

Having clear boundaries on where variables can be accessed can tell the compiler, that at the end of this scope it is safe to garbage collect these variables.

3. Ownership

If all our variables are global, it means that anyone can change them.

For eg. in one of the maintanence projects I had to make a Custom Event to broadcast an event to another part of the application itself. But whatever I did, I could not get the custom event to fire. What happened was that someone else had already declared a class named CustomEvent (they wanted to customise the Event class, so be it!) on the global window and it was overriding my interpretation. Except the other person (git blame) who did it, did not even know that a function named CustomEvent actually existed in JavaScript.

Imagine this happening to your variables, randomly at runtime. This is what happens if we do have some sort off ownership for the variables and functions that we write.

JavaScript has two kinds of scope:

  1. Block Scope
  2. Function Scope

We will talk about function scope first.

Function Scope

Function Scope means that any variable declared would be available inside the function. This has existed and was widely used from olden times in JavaScript.

1function action() {
2 var a = 2;
3 ... // actions
4 // a can be accessed anywhere in this function.
5}

Hoisting

Wherever you declare a variable, JavaScript would proceed to hoist these upto the top of their scope. But let us be semantically correct here. JavaScript does not move the variables physically all your code remains the same. The compiler just picks the variables in it’s current scope and declares them in compiler time with the value undefined.

This is how you can access a variable before declaration and get undefined as an answer.

You don’t have to be afraid of hoisting. In fact hositing is what helps you when declaring functions in whichever order you want. Since the compiler would hoist it to the top anyway, it does not matter which order you declare it in. But with variables declared with var, it is important to write short precise functions.

Also, note that only declarations are hoisted, they would not take any value if used before initialisation.

Block Scope

This is widely used from ES6. Block refers to a {} in a program.

Block Scope means that the variables defined inside a {} can only be used inside it. Variables can be put in block scope by declaring them with let or const.

Note that functions also form block scope.

1function action(limit) {
2 const a = 10; // a can only be used inside this function
3 if (a < limit) {
4 const b = a + 2; // b can only be used inside this if block, a can also be used here as this block is inside the execution context of a's block
5 return b;
6 }
7}

Hoisting

Does hoisting happen in Block Scope? Yes. But if we try and use a variable before it’s actual declaration, we get a ReferenceError.

That does not make any sense. If they are hoisted, shouldn’t they be undefined?

To get ahead of this question, JavaScript spec defines something known as a Temperal Dead Zone (TDZ). This is the difference of when it is memory (remember: Hoisting is just compiler putting variable declarations in memory) and it’s actual declaration in code. When a block scoped variable in TDZ is accessed, it throws a ReferenceError.

Closures

JavaScript is a language that treats functions as first class citizens. This is a part and parcel of the functional languages. Functions are just objects in JavaScript and they can be assigned to variables, pass them to functions or return them from functions itself.

Let’s look at these conditions one by one:

  1. Assigned to variables
1const foo = function(a) {
2 return a++;
3};

Here function is assigned to variable foo, to invoke this function we have call foo(). foo here is a reference to the function and can be reassigned or assigned to some other variable.

  1. Pass them to functions

We just saw that functions can be assigned to variables. This is essentially an easy by product of the same. You can pass these references around as you would do with any other object.

Here, you can see that inc is being passed into the counter function which in turn is invoking it. You might wonder why we have to take this approach instead of just calling inc directly from counter. The difference is that now we can control the factor of how much the counter is going to increment by from the outside. That is, we can pass another function that increments by 2 and Boom! we have a counter that adds by a factor of 2 instead of 1. We can do this without changing the function at all.

We are not invoking it when write inc, the incrementing is only when the brackets are attached to it.

  1. Return functions

This is going to be longer than the other ones, but bear with me here.

With the last example, we discussed how we can change the counter function by passing in different functions. Let us look at how we might achieve that result:

We just created two functions: inc and incBy2. The first function increments by 1 and second increments by 2. But I guess we can agree that this is not the most elegant approach. If we had to create a function that adds by 3, then we would require a third function. How can we create a single function for this purpose?

Let us look at the simplest approach first:

Well, this does the work. But this is breaking the expectation we had set for ourselves. The whole objective of passing a funtion to counter was the fact that counter did not need to know the factor which was being increment or any operation being performed. By passing factor into counter, we have broken that encapsulation. We need better ways to create dynamic functions that we can pass into counter.

If this seems natural for you, then Congrats! 🎉🍾 You have successfully understood closures. If it doesn’t read on:

createInc is a function that returns a function, let that sync in; A function that returns a function.

What we have to be concerned here is the variable factor that is passed in. If you look of the call stack of this program, you can see that createInc is added to the stack and is popped as soon as the function inside it is returned. But the returned function is still using factor in runtime. How is that retained?

When a function is created, the function stores both it’s local function and the context the function was created in. This context is known as the closure environment. A function when it is created, it stores the local variables and the closure scope it was created in. This closure scope is garbage collected only when the function itself is collected. This is part execution context of the function.

Does this change the way I write code?

Well, it should. Scopes and Closures are some of the most integral cornerstones of the language. It can and should influence the way you think about the language and declarations.

Is there something I missed? Something wrong? Something good? Ping me on Twitter