There are many buzzwords 🤯 in JavaScript language but the biggest one is closure. It is the subject of many job interview questions. Here, I will talk about closure and scope, and illustrate its concepts with simple examples.
🛑 🤔 Scope
When someone tells you something is or isn’t in scope of a project, what does that mean?
Imagine a periscope or a telescope when you think of the answer. These instruments show us all sorts of things within the confines of the lens it has: it’s in scope.
If it’s outside the scope, you can’t see past the diameter of the lens. And shining a light on something outside the diameter is not possible. Think about this when dealing with the three very important types of scope in JavaScript are: local, global and lexical.
🛑 🤔 Local Scope
Local Scope is the smallest of the three scopes.
⚠️Remember: When you declare a function, anything inside the brackets ({}) is considered to be local to the function.
👉 When the JavaScript engine reads the function it will declare the variables; when it ends it will destroy the variables.
function greeting() {
var websiteName = 'Digital Career';
return `Hello ${websiteName}`;
}
console.log(greeting()); // Hello DC
console.log(websiteName);
// ReferenceError: websiteName is not defined
As you can see, when console.log() the outcome of the invoked greeting function, you are able to access the websiteName
after the function was executed.
👉 This gives the ‘Hello DC’
string that you were looking for. The console.log() of the variable that was declared inside the function throws an error because it’s not defined.
As mentioned already, the reason why websiteName is undefined is because variables are created inside functions when they are invoked
and then destroyed when the terminal statement runs. Anything outside of the function does not have access to what is inside the function unless it has a special setup.
🤔 Global Scope
This next scope is pretty much a literal translation of the phrase. A global scope takes the items declared outside of a function and reserves them in a space where all the scripts and methods and functions can access and use them for their logic.
let counter = 0; // global -- declared outside function
const add = () => { // function declaration
let counter = 0; // local -- declared inside function
counter += 1;
// counter increased by 1 -- which counter variable increased?
return counter;
}
add(); // invoke
add(); // three
add(); // times
console.log(counter) // is this 3 or 0? Why?
🤔 What does the code above do if we console.log() the counter at the end of the code? What do you expect to happen?
Have a look at the steps and examine the code:
👉 1. Counter variable declared and initiated in the global environment.
👉 2. Add function declared in the global environment.
👉 3. Add is invoked.
👉 4. Counter variable declared and initiated in the local
environment.
👉 5. The local counter increases by 1 ⇐ why local and not
global?
👉 6. Counter is returned. Function terminates.
👉 7. Add is invoked again
👉 8. Walk through steps 4 to 6 again.
👉 9. Repeat steps 3 to 6 again.
👉 10. console.log(counter); ⇐ What is returned?
☝️ Because the function terminates when the counter is at 1
every time, the local counter variable is redeclared and re-initiated at 0
every time the function runs.
🛑 No matter what happens, the counter will always stop at 1
on the local level.
If a function finds a variable within its scope, it doesn’t look to the global scope for the variable – so the global variable never changes.
So, the console.log()
will output 0
as your closest defined variable within that statement’s environment is in the global environment.
🛑 Lexical Scope
The lexical scope is one of the most fundamental concepts in JavaScript. It’s the idea that the creation of a function or of a variable will be accessible to certain parts of the code and then inaccessible to other parts of the code.
It all depends on where the declaration of each variable and function is.
Have a look at the below block of code:🔻
const init = () => { // <== This is our outer function
const var1 = 'Digital'; // outer scope
const second = () => { // <== This is our inner function
const var2 = 'Career'; // inner scope
console.log(var1); // Digital
console.log(var2); // Career
return var1 + " " + var2;
};
// console.log(var2); // undefined
return second();
};
init();
Here we have a set of nested functions. The init()
function declares a variable called var1
, declares a function called second and invokes second()
.
When the compiler passes through this code the first time, it takes a high level look at what we have:
🔻
1. init() function
2. invoke init()
At this point, we can’t see anything else inside the init() function – we just know the function exists. When our init() func is invoked, the compiler takes another high level look at what’s inside the function:
🔻
1. var1
2. second() function
3. invoke second()
The init()
function knows nothing about what is going on inside the second() block. It can only see what is in its lexical environment – its surrounding state.
Each nested function is in a smaller container, which resemble a set of Russian matryoshka nesting dolls. As the dolls only know about what’s going on inside their container and what’s already occurred or declared/read in the parent. The largest doll only knows that the next doll in its container exists. It doesn’t know about any of the other dolls in the set,but just what’s in its lexical environment (its state)
and what’s already happened (the outer scope)
.
Remember two things:🔻
👉 The outer scope can’t see the inner scope.
👉 The inner scope has access to the outer scope.
Because the outer scope can’t see what’s going on in the inner scope, we can safely say that this is a one-way relationship.
🛑 👉 Inner can see and use variables from the outer scope, but outer can’t see inner. This is called lexical scope.
The beauty of lexical scoping is that the value of a variable is determined by its placement in the code.
The functions look for the meaning of a variable inside it’s local environment first – if it can’t find it, it moves to the function that defined that function. If it can’t find it there, it moves up the chain to the next defined function.
- This becomes a very important concept in JavaScript that will come up time and again as you learn more about JavaScript frameworks and how they work. You can pass down from the outer, but you can never pass “up” in the other direction. This is very important as we get to the major topic at hand: closure.
What is a Closure ? 🤔
A closure, also lexical closure or function closure, is a technique for implementing lexically scoped name binding in a language with first-class functions. It is a record storing a function together with an environment.
The environment is a mapping associating each free variable of the function (variables that are used locally, but defined in an enclosing scope) with the value or reference to which the name was bound when the closure was created.
It allows the function to access those captured variables through the closure's copies of their values or references, even when the function is invoked outside their scope
Closures are important because they control what is and isn’t in scope in a particular function, along with which variables are shared between sibling functions in the same containing scope.
🛑 Understanding how variables and functions relate to each other is critical to understanding what’s going on in the code, in both functional and object oriented programming styles.
Coding in JavaScript without an understanding of closures is like trying to speak English without an understanding of grammar rules — you might be able to get your ideas across, but probably a bit awkwardly.
- Closure’s definition is very similar to that of lexical scope.
- The main difference between the two is that closure is a higher order function and lexical scoping is not.
- A higher order function has one basic characteristic: it either returns a function or uses a function as a parameter.
🛑 Closure is a function that can access its lexical scope, even when that function is being invoked later.
🛑 Both closure and lexical scope have their own variables, can access a parent function’s variables and parameters, and can use global variables.
Let’s have a look at the following code:
function greeting() { //outer scope (parent function)
const userName = "DigitCareer1346"; // parent variable
function welcomeGreeting() { // inner function
console.log("Hello, " + userName); // accesses parent var
return "Hello, " + userName; // terminal statement
}
return welcomeGreeting; // returns a function (which makes it HOF)
} // end of greeting()
const greetUser = greeting(); //
greetUser(); // Hello, DigitCareer1346
-
greeting()
function exists, but we don’t know the contents yet. -
greetUser
exists, but don’t know contents yet -
greetUser()
– this invokes the previous line, which, in turn, invokes thegreeting()
function. -
userName
declared -
welcomeGreeting()
exists, but don’t know contents yet - Return statement below the
welcomeGreeting()
block returns that very same function -
console.log(‘Hello, ‘ + userName);
the console.log here can access the parent scope to get the value ofuserName
- Terminal statement that ends the function and destroys the meaning of the variables inside the code block.
In this code, we are passing around information by nesting functions together so that the parent scope can be accessed later.
Let's Summarise
In my brief article, I pointed out the confusing & important JavaScript subject: Scope and Closures.
🛑 Remember:
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.
👉 To use a closure, define a function inside another function and expose it. To expose a function, return it or pass it to another function.
👉 The inner function will have access to the variables in the outer function scope, even after the outer function has returned.
In JavaScript, closures are the primary mechanism used to enable data privacy. When you use closures for data privacy, the enclosed variables are only in scope within the containing (outer) function. You can’t get at the data from an outside scope except through the object’s privileged methods.
In JavaScript, any exposed method defined within the closure scope is privileged.
Well, there’s lots of ways to learn it. I hope my guide was helpful to you!
Good luck in your study on closures & Happy Coding!