Closures - A quick overview
From many of my software developer connections on LinkedIn, I keep seeing closures pop up as a key fundamental in being a strong JS developer. For this reason, I'm writing this article as a reference to my learning over the last few days so that I can revisit this to consolidate my knowledge.
There are a couple of topics I want to briefly cover before moving onto Closures: Functions are objects & Higher Order Functions.
Functions are objects
What do I mean by this? Well functions are objects in JavaScript... because they can have properties and methods just like any other object. What distinguishes them from other objects is that functions can be called. e.g function.call()
Functions are first class citizens in JS. There are three properties that make functions, a first class citizen in JavaScript:
1) We're able to assign to variables these functions.
2) We can pass these functions into arguments.
3) We can return functions as values from another function.
Higher Order Functions
A Higher Order Function (HOF) is a function that either takes a function as an argument or returns another function. There are 3 kinds of functions in JavaScript.
1) function ()
2) function (a,b)
3) function hof() { return function () {} }
This helps us maintain DRY.
function multiplyBy(a) {
return function (b) {
return a * b
}
}
// can also be an arrow function
//const multiplyBy = (a) => (b) => (a) * (b)
const multiplyByTwo = multiplyBy(2)
const multiplyByTen = multiplyBy(10)
multiplyByTwo(4) // 8
multiplyByTen(5) // 50
//OR
multiplyBy(36)(63) // 2268
Recommended by LinkedIn
Closures
Closures allow a function to access variables from an enclosing scope or environment even after it leaves the scope in which it was declared.
In other words, a closure gives you access to its outer functions scope from the inner scope. The JavaScript engine will keep variables around inside functions that have a reference to them, instead of "sweeping" them away after they are popped off the call stack.
function a() {
let grandpa = 'grandpa'
return function b() {
let father = 'father'
let random = 12345 // not referenced, will get garbage collected
return function c() {
let son = 'son'
return `closure inherited all the scopes: ${grandpa} > ${father} > ${son}`
}
}
}
a()()()
// closure inherited all the scopes: grandpa > father > son
Now, you're asking why is this useful? Well there are two main reasons:
1) Memory Efficiency
Using Closures can make your code more memory efficient. We can store specific data that we need to access multiple times.
See below example:
function inefficient(index) {
const bigArray = new Array(7000).fill('😄')
console.log('created!')
return bigArray[index]
}
function efficient() {
const bigArray = new Array(7000).fill('😄')
console.log('created again!')
return function (index) {
return bigArray[index]
}
}
inefficient(688)
inefficient(1000)
inefficient(6500)
const getEfficient = efficient()
getEfficient(688)
getEfficient(1000)
getEfficient(6500)
// created!
// created!
// created!
// created Again!
// '😄'
// inefficient created the bigArray 3 times
// efficient created the bigArray only once
2) Encapsulation
Encapsulation means the restriction of direct access to some of an object's components. It hides as much as possible of an object's internal parts and only exposes the necessary parts to run. Why use encapsulation?
I need to revisit a lot of these concepts until they are instilled in my mind, but for now I'll be moving onto Prototypal Inheritance.