The following passage is form “Eloquent JS” book:
“Being able to reference a specific instance of a local binding in an enclosing scope is called closure. A function that references bindings from local scopes around it is called a closure. This behavior not only frees you from having to worry about lifetimes of bindings but also makes it possible to use function values in creative ways.”
function multiplier(factor) {
return number => number * factor
}
let twice = multiplier(2)
console.log(twice(5))
// expected output is 10?
What?!
Let’s checkout a fireship video here called “Closures Explained in 100 Seconds”
Closures are functions that can access values outside of their own curly braces. In order to call a function the JS interpreter needs to know about the function itself and any other data from the surrounding environment that it depends on. Everything needs to be neatly closed up in a box until it can be fed into the machine.
let b = 3 // "free variable" parent scope
function impureFun(a) {
return a + b // captures from above
}
Another example
const state = 'rabbit' // lexical environment
function callMe() {
return `Hello ${state}` // captures from lexical environment
}
Example with no external data. This is fully self-contained closed expression that is not a closure. When this is called it gets pushed onto the call stack where it is executed and its internal memory is only kept in memory (stack memory) until it is popped back off of the call stack.
function pureFun(a, b) {
return a + b
}
But what if this function references data outside of its own scope, like from the global environment or an outer function. We get an open expression that references other free variables throughout the environment.
let b = 3 // "free variable" parent scope
function impureFun(a) {
return a + b // captures from above
}
In order for the interpreter to call the “impureFun” function above, and also know the value of these free variables, it creates a closure to store them in a place of memory so that they can be accessed later, that area of memory is called the heap, and unlike the call stack which is short-lived, it can keep data indefinitely, then decide when to get rid of it later with the garbage collector.
A closure is not just a function, it is a function combined with it’s outer state or “lexical environment.” Closures require more memory and processing power than a pure function but you will come across many practical reasons to use them. The most important one being data encapsulation to prevent leaking or exposing data where it is not needed.
Note: What does it mean for a function to not be a “pure” function? Depends on a variable outside of its scope.
Create a closure by defining an outer function that contains the state and then an inner function that operates on it.
function outer() {
const state = 'rabbit' // lexical environment
function callMe() {
return `Hello ${state}` // captures from lexical environment
}
}
The data contained here will not leak out of the surrounding environment. The inner function has access to data defined in the outer function’s scope but the outer function does not have access to the inner function.
In addition, many JavaScript APIs are callback based. You can use closures to create a function factory that takes an argument then returns a brand new function that can then be passed along to other functions that expect a callback.
function alertFun(message) {
return () => {
alert(`Hello. ${message}`)
}
}
const alertMom = alertFun('hi mom')
alertMom()
Beyond 100 seconds: one of the most famous JS trick questions. What does the following code log out?
for (var i = 0; i < 3; i++) {
const log = () => { // log is a closure; not a pure function
console.log(i)
}
setTimeout(log, 100)
}
Seems like it should log out 0, 1, 2, but it actually logs out 3, 3, 3
To understand why this happens we need to understand the difference between “var” and “let” keywords.
Variables bound with “var” get hoisted to global scope. We have a global variable we are mutating over and over again. So we are capturing the reference to the global variable.
If we changed the code above to “let” we would get “0, 1, 2” because “let” is block scoped meaning it is local to the “for loop” and can’t be accessed outside of it.
A closure is the combination of a function and its lexical environment.
Note: the “lexical environment” is the context when the function was defined.
In the case of “let,” the closure is capturing the “log” function along with the variable “i” for each iteration of the loop which would be “0, 1, 2.” If we didn’t have a closure here, JS would allocate that “i” variable in memory, in the call stack and then immediately release it. but because we do have a closure it stores the variable in the heap memory, (instead of the call stack memory), so it can be referenced again when that closure is called by the timeout in the future.
When “var” is used it is capturing the reference to the global variable. The reason it logs 3 three times is because the timeout doesn’t run until 100 milliseconds later, long after the “for loop” has completed and iterated up to 3.
You can examine the behavior in the browser’s dev tools by adding a debugger to the closure.
There’s a “techsith” video here that is 13 minutes long and is fine on 2x speed. Form 2016 way before ES6 “let” and “const” but his example on “JSFiddle” still holds up.
var passed = 3
var addTo = function () {
var inner = 2
return passed + inner
}
console.log( addTo() )
// expected out is 5
Wanted something more recent so I watched this video here, which I have watched before and didn’t get it.
var me = "Bruce Wayne"
function greetMe() {
console.log("Hello, " + me + ".")
}
greetMe()
// result is Hello, Bruce Wayne.
Also from 2015?! So let’s try “WebDevSimplified” here
const myName = "Kyle"
function printName() {
console.log(myName)
}
printName()
// expected Kyle
Closures are not available in all other languages.
let myName = "Kyle"
function printName() {
console.log(myName)
}
myName = "Joey"
printName()
// expected Joey
Functions have access to their outer scope. Scope can be accessed outwards but not inwards as seen way back in the “LearnWebCode” video here. Scope begins inwards and moves outwards. It moves in one direction and is a “one-way street.” As an example it could move “up the scope chain” from an “if statement’s block scope” to “function block scope” to the “global scope.” Outside code cannot reach inwards for variable.
let myName = "Brad"
function amazingFunction() {
//let myName = "Brad Junior"
if (2 + 2 === 4) {
//let myName = "Brad the 3rd"
console.log(myName)
}
}
amazingFunction()
// expected Brad
Note: These are three separate variables in the code above. Even though they share the same identifier or label, the “let” keyword is creating three completely separate variables each within it’s own scope. If you do want to update the value then remove the “let” keyword from one of the inner scopes.
Closures in this sense are very simple. But back in the “Web Dev Simplified” video, you typically see closures as functions inside of other functions.
function outerFunction(outerVariables) {
return function innerFunction(innerVariable) {
console.log("Outer Variable: " + outerVariable)
console.log("Inner Variable: " + innerVariable)
}
}
const newFunction = outerFunction('outside')
newFunction('inside')
Now I’m confused again
// fetch example
function outerFunction(url) {
fetch(url).then(() => {
console.log(url)
})
}
const newFunction = outerFunction('outside')
newFunction('inner')
His main point: When you have a function defined inside another function, that inner function has access to the variables and scope of the outer function even if the outer function finishes executing and those variables are no longer accessible outside that function.
But I still don’t understand the original code from the Eloquent JS book.
function multiplier(factor) {
return number => number * factor
}
let twice = multiplier(2)
console.log(twice(5))
// expected output is 10?