Welcome to Part 6 of this review of the Pluralsight and Front End Masters course Advanced JavaScript by Kyle Simpson.
Kyle Simpson is Head of Curriculum for MakerSquare and an evangelist of the open web. He lives in Austin, Texas, and is passionate about all things JavaScript.
He’s written 8 books published by O’Reilly, including six books in the You Don’t Know JS series.
Kyle teaches JavaScript and has eight courses recorded by Front End Masters.
He’s a public speaker, and contributes to the world of OSS.
In this series:
Part 1 – Introduction
Part 2 – Scope
Part 3 – Lexical Scope
Part 4 – Block scoping
Part 5 – Dynamic Scope, Hoisting and this
Part 6 – Closure
Part 7 – Object Prototypes
Closure
Closures
(Also see Chapter 5 of the Scope & Closures book)
Kyle begins with an anecdote: over 20 years ago when he was in high school learning C, he would find mentions of pointers in books and get the sense that they were a magical level of enlightenment.
You could write C without understanding pointers, but you could never experience the true power of C without the real understanding of pointers.
He waited for the day when the magic lightbulb would go on and he would be elevated right up to the upper echelons of C gurus. And then that day arrived!
Okay maybe I exaggerated that story just a little bit. But the pointer is, I mean the point is, that this is how many people feel about closures.
There is a long perpetuated fairy tale that the whole language opens up to you as soon as you understand closure.
Kyle say if you’re hoping for that magical enlightenment moment now, “it’s going to feel a little anti-climatic”.
Everything we learned earlier in the course is the basis for how closures work.
Closure is a mathematical concept from lambda calculus. There’s a mathematical definition but we don’t need to know it.
Kyle’s “what you need to know” definition is:
Closure is when a function “remembers” its lexical scope even when the function is executed outside that lexical scope.
And here’s a code sample to illustrate:
function foo() {
var bar = “bar”;function baz() {
console.log(bar);
}bam(baz);
}function bam(baz) {
baz(); // “bar”
}foo();
Remember we talked about scope bubbles earlier? The first 9 lines are one bubble, and we’re executing outside of that on line 12. But we can still access that variable. And that is closure!
Closure Examples
We can return functions from functions:
function foo() {
var bar = “bar”;
return function() {
console.log(bar);
};
}function bam() {
foo()();
}bam();
In this example see the line with foo()();
The first () gets the inner function back. In the micro-second between the first and second (), that function object is transported outside of its lexical scope.
Here’s a setTimeout example:
function foo() {
var bar = “bar”;setTimeout(function() {
console.log(bar);
},1000);
}foo();
Inside of the engine somewhere there’s a setTimeout utility, which get a callback called see me.
It executes our function well outside of its lexical scope, but the function still remembers the lexical scope.
We also see a click handler example:
function foo() {
var bar = “bar”;$(“#btn”).click(function(evt) {
console.log(bar);
});
}foo();
Kyle describes closure as a “necessary mechanism for a language with first-class functions as values”
There are many examples of Closure in this clip.
Next Kyle shows and explains a shared scope example.
Then there is a nested scope example.
And then there is the canonical example: loops
for (var i=1; 1<=5; i++) {
setTimeout(function(){
console.log(“i: ” + i);
},i*1000);
}
The aim of this code is to print out i1, i2, i3, i4, i5 with a one second pause between each output.
It actually prints out “i66666”
Try it yourself. What’s missing from this example that’s preventing it from working?
Kyle says:
“when closure’s not behaving the way we want, it’s because we have an incomplete understanding of what the closure would really mean.”
The i at the end of the loop ends up as a 6, but why don’t we have five different i values?
What we are actually getting is five different anonymous setTimeout functions that are closing over the exact same global scope.
So how can we make it have a different scope for each iteration? We’ve already covered that. Functions create scope. We can use an IIFE.
for (var i=1; 1<=5; i++) {
(function(i){
setTimeout(function(){
console.log(“i: ” + i);
},i*1000);
})(i);
}
More Closure Examples
What if we use the let keyword? Will this work?
for (let i=1; i<5; i++) {
setTimeout(function(){
console.log(“i: ” + i);
},i*1000);
}
Is the let keyword binding an i to the for loop?
Yes, it is binding the i to the for loop, and it’s also re-binding that i for each iteration of the for-loop!
This will indeed work without any need for an IIFE. This code is now much more readable don’t you think?
An audience member asks about the performance hit caused by this code getting transpiled to ES5 code. This code would create many try catch blocks.
Kyle say ES6 has the capability to optimize this binding, but our transpiled ones will take a performance hit.
Now one last example. Is this an example of closure?
var foo = (function(){
var o = { bar: “bar” };
return { obj: o };
})();console.log(foo.obj.bar); //”bar”
Take a look at the above definition and ask yourself whether this meets that definition.
No spoilers here – watch the course to find out the answer.
Closure: Module Patterns
First we have the classic module pattern:
var foo = (function(){
var o = { bar: “bar” };
return {
bar: function(){
console.log(o.bar);
}
};
})();foo.bar(); // “bar”
The classic module pattern has two key characteristics:
- An outer wrapping function which get executed
- One or more functions that get returned from that function call (inner functions that have a closure over the inner private scope)
(See the Modules section of Scope & Closures Chapter 4)
Kyle says he hates it when people call this a class, and we’ll come back to that topic later.
We also see the modified module pattern (a.k.a. revealing module pattern):
var foo = (function(){
var publicAPI = {
bar: function(){
publicAPI.baz();
},
baz: function(){
console.log(“baz”);
}
};
return publicAPI;
})();foo.bar(); // “bar”
Another variation is the modern module pattern:
define(“foo”, function(){
var o = { bar: “bar” };
return {
bar: function(){
console.log(o.bar);
}
};});
Kyle talks a bit about Require JS here, making the point that all loader libraries are essentially implementations of the module pattern.
Finally the ES6 module pattern:
foo.js
var o = { bar: “bar” };
export function bar() {
return o.bar;
}
To import:
import bar from “foo”;
bar(); // “bar”module foo from “foo”;
foo.bar(); // “bar”
Quiz: Closure
Time to test your understanding:
- What is a closure and how is it created?
- How long does its scope stay around?
- Why doesn’t a function callback inside a loop behave as expected? How do we fix it?
- How do you use a closure to create an encapsulated module? What’s the benefit of that approach?
Once again, see the course for the answers.
Exercise 2
Kyle invites the workshop attendees to improve the structure of a messy note taker app with the classic module pattern.
He then presents a possible solution.