Simple Beginner’s Guide to JavaScript Closure
Closure in JavaScript can be confusing for newcomers. However, it doesn’t have to be that way. Here is a quick rundown of what you need to know.
First, let’s define closure using a definition that is easy to understand.
Closure
- Closure is when a function remembers its lexical scope even when the function is executing outside that lexical scope.
Remember, functions (function references) can be passed around. Therefore, they can be called from anywhere. However, you need to be able to easily figure out the scope that is accessible within the function. Without closure, programming in JavaScript would be extremely tedious.
Let’s take a look at a simple example and discuss it together.
Example 1
function f1() {
var v = "val 1";
// define f2
function f2() {
console.log(v);
}
// call f3 and pass a reference to the function f2 into it
f3(f2);
}
function f3(fn) {
// call the function that was passed in
// the function is called here, but
// remembers the environment (i.e. scope)
// surrounding its definition
fn();
}
f1(); //=> "val 1"
First, the function #f1 is defined. Second, within #f1, the function #f2 is defined. Then, the function #f3 is defined. However, nothing is executing yet. Once execution starts, the function #f1 gets called. The function #f1 calls the function #f3 and passes a reference to #f2 that is defined within #f1’s scope. I haven’t mentioned var v, but it exists within #f1’s scope and is just sitting there. Ok, so the function #f3 is now running and was passed a reference to the function #f2 from within #f1. But, within #f3, the function #f2 is not called #f2, it is called #fn. The first and only thing #f3 does is call the function #fn (i.e. #f2). The function #f2 prints out the value of var v to the console. So, #f2 is executing within #f3 under then name #fn and has access to everything within #f1. This is closure and can be a serious gotcha to some people.
Perhaps your thinking the following…
Now, let’s take a look at another example that will often trip people up.
Example 2
for (var i = 0; i <= 5; i++) {
setTimeout(function () {
console.log("i=" + i);
}, i * 1000);
}
//=> i=6
//=> i=6
//=> i=6
//=> i=6
//=> i=6
//=> i=6
In example 2, the for-loop executes 6 times. Each time it executes it calls #setTimeout and passes an anonymous function to it. Please note, that anonymous function has not executed and won’t be executed until long after the for-loop has completed its execution. Now, let’s fast forward to the end of the first timeout. At that time, the anonymous function executes. It simply logs the value of “i” to the console. But, the tricky part is understanding what the value of “i” is. Let’s think about it. From the first example, we know that the anonymous function remembers its scope. In this case, the scope includes the value of “i”. The question is does “i” equal the value of when the anonymous function was passed to SetTimeout or does “i” contain the latest value? The answer is “i” contains the value that it has when the anonymous function starts executing. In other words, it’s the “now” value because it is looked up by the anonymous function when it executes.
Of course, this may not be what we intended, so let’s take a look at another example that gets around this gotcha.
Example 3
for (var i = 0; i <= 5; i++) {
(function (x) {
setTimeout(function () {
console.log("i=" + x);
}, x * 1000);
})(i);
}
//=> i=0
//=> i=1
//=> i=2
//=> i=3
//=> i=4
//=> i=5
Each time the loop is executed, the immediately invoked function expression (IIFE) is passed the current value of “i”. When passed in to the IIFE, the function parameter “i” is named “x”. However, it could have just as well been named “i”. The scope of the anonymous function is the IIFE. Therefore, the value of “x” when the IIFE began executing (i.e. at each loop iteration) is used. Remember, the IIFE is executed at each loop iteration and not when all of the iterations have completed. This is in contrast to example 2.
In example 4, I am going to show a slight variation where “x” is renamed to “i”.
Example 4
for (var i = 0; i <= 5; i++) {
(function (i) {
setTimeout(function () {
console.log("i=" + i);
}, i * 1000);
})(i);
}
//=> i=0
//=> i=1
//=> i=2
//=> i=3
//=> i=4
//=> i=5
Example 4 and example 3 are equivalent. Don’t let it trip you up.
In example 5, we look at a slight variation that may make the semantics clearer.
Example 5
var fn = function(i) {
setTimeout(function() {
console.log("i=" + i);
}, i * 1000);
};
for (var i = 0; i <= 5; i++) {
fn(i);
}
//=> i=0
//=> i=1
//=> i=2
//=> i=3
//=> i=4
//=> i=5
Example 4 and example 5 are equivalent. The contents of the loop are pulled out into a separate function. Here, we can clearly see that each time #fn is called with the value of “i”, a new scope is created. New function call means new scope. The value of the parameters passed into a function at the time it is called become the values it uses when executing (e.g. 0, 1, 2, 3…).
In example 6, we use the keyword “let” to completely avoid this issue.
Example 5
for (let i = 0; i <= 5; i++) {
setTimeout(function () {
console.log("i=" + i);
}, i * 1000);
}
// i=0
// i=1
// i=2
// i=3
// i=4
// i=5
By using “let” instead of “var”, the value of “i” is remembered for each iteration through the loop. Internally, you can imagine the the V8 or the browser converts it into the example given in example 4.
Hopefully, this clears up closures. Here is a bouquet of kittens.