Closures in Javascript are one of those important concepts and also many struggles to get their heads around. In the following article, I will explain everything about closures and walk you through along with code examples.
What is a closure?
A closure is the combination of a function bundled together (enclosed) with references to its surrounding state (the lexical environment).
In other words, closure gives you access to the outer function's scope from the inner function. i.e. access to the variables and functions which are present in its outer scope. So even when this function is executed in some other scope then also it remembers it outer scope which was present originally.
code example of lexical scope:
function outer() {
var x = 10
function inner() {
console.log("x: " + x)
}
inner()
}
outer()
In the above code, outer()
creates a local variable called x
and function called inner()
. The inner()
is defined inside the outer()
and available locally. Also the variable x
available inside inner()
only.
So the above code will give output as below:
x: 10
So the above code was an example of how lexical scope
works.
code example of Closure:
function outer() {
var x = 10
function inner() {
console.log(x)
}
return inner
}
let init = outer()
init() // 10
This code is as similar as the above code, the difference is that here we are returning inner
, while In the above we were invoking inner()
.
Let's see what's happening!
- Here, when
outer()
is invoked it createsvariable x
and assigns it the value10
. - In the next line there is a function declaration, so nothing to execute.
return inner
will return the entireinner
function.Execution of
outer()
is done here.Important point to note is that all variables inside the function have a life span until the function execution.
- That means
variable x
can be accessed untilouter()
is executing.
Then how does it giving output 10 when
init()
is invoked?
This is where closure comes into the picture!!
- The
inner
function can refer to the variables and function of its outer function i.e. fromouter
function. - In other words, the
inner
function preserves the scope chain of the enclosing function at the time the enclosing function was executed and thus can access the enclosing function’s variables.
Every closure has three scopes.
- Local Scope (Own scope)
- Outer Functions Scope
- Global Scope
Let's see by example:
function outer() {
var x = 10
function inner() {
console.log(x)
}
return inner
}
let init = outer()
console.dir(init)
- Do
console.dir(init)
and see the scopes of function. Inner function has three scopes.
- Inner function itself
- Outer function
- Global
What if we take parameters in function? Does it work the same?
function outer(a) {
function inner(b) {
console.log(a + b)
}
return inner
}
var init = outer(3)
init(2) // 5
- So the answer is yes, it will still work because
inner
will have access tovariable a
.
function outer(a) {
function inner(b) {
console.log(a + b)
}
return inner
}
outer(3)(2) // 5
- We can also invoke a function like this. Both the ways are the same.
Advantages of closures
- Data Hiding & Encapsulation
- Function Currying
- Module pattern
I am not going to cover all of these, I will cover Data Hiding & Encapsulation only.
What is Data Hiding and Encapsulation?
Let say we have some variable and we want to have some data privacy over it. i.e. other functions can not have access to that variable that is what data hiding is.
Let's have an example :
var counter = 0;
function incrementCounter() {
counter++;
}
- In the above code, We have a
variable counter
which can be increased by functionincrementCounter
. - But this
variable counter
is accessible for everyone. So there is no data privacy here. - So here comes data hiding, we want that nobody can access this
variable counter
. - So we will form closure around
variable counter
to ensure that it can not access by other functions.
Let's see code example:
function count() {
var counter = 0
return function incrementCounter() {
counter++
console.log(counter)
}
}
var incrmentCount = count()
incrmentCount() // 1
incrmentCount() // 2
incrmentCount() // 3
incrmentCount() // 4
- Here we wrapped
variable counter
insidecount
function and returningincrementCounter
function. - So when
count()
is invoked, it creates a new copy ofvariable counter
. - Now, when
incrmentCount()
is invoked it increments count each time it gets executed. - In the above example, We are invoking
incrmentCount()
four times, so the value ofvariable counter
will be 4.
Point to note down here is that whenever we invoke
count()
and store in new variable, it creates a new copyvariable counter
for each variable.
Code example:
function count() {
var counter = 0
return function incrementCounter() {
counter++
console.log(counter)
}
}
var counter1 = count() // Counter 1
var counter2 = count() // Counter 2
counter1() // 1
counter1() // 2
counter1() // 3
counter2() // 1
counter2() // 2
- Here are
counter1
andcounter2
variables and both have different copies ofvariable counter
. - Try to do
console.dir(counter1)
andconsole.dir(counter2)
and observe the scopes ofcounter1
andcounter2
.
Scope of counter1
Scope of counter2
Disadvantages of closures
- When we create closures, it creates new variables every time we execute the function and those variables are not garbage collected.
- So it may lead to memory leaks.
Though, some latest browsers have smart garbage collection mechanisms. So what it does is that suppose if we have declared variables in outer function and we are not using them in inner function so it smartly garbage collects those variables and references to only used variables.
Code example:
function outer() {
var a = 10,
b = 13,
c = 20
return function inner() {
console.log(a, b)
}
}
var x = outer()
x() // 10 13
- In the above code, there are three variables in the
outer
function. variablea, b, c
. - But in the
inner
function we are accessing onlya
andb
variables. - So in the closure, it will store only
a
andb
andc
will not be stored. - try doing
console.dir(x)
in console and observe the scopes.
Last but not least a challenge for everyone. Try to do it with the use of closures.
Print 1 to 10 after each and every second i.e. 1 after 1 second, 2 after 2 seconds like that. Also note that don't use
let
as the initializer for the loop. Try to form closure and print. Modify below-given code.
for (var i = 0; i < 10; i++) {
setTimeout(() => {
console.log(i)
}, i * 1000)
}
So that was all for this blog and I will see you in the next one. Till then you can read more about closures with setTimeout
, Currying
, Module Pattern
etc.