Seeing as MDN’s page on closures defines it extremely well, I’m not going to reinvent the wheel here:
A closure is the combination of a function bundled together (enclosed) with references to its surrounding state (the lexical environment).
Let’s unpack this concept a bit.
The first thing I want to point out is that whenever you define a function, you are making a choice about its lexical environment, whether intentional or unintentional, conscious or…maybe you’re unconscious. I don’t know.
In many cases, the lexical environment of a function is just the global scope, because that is where you created it. Don’t worry, there’s nothing wrong with that.
**But the value of a closure becomes more obvious when you intentionally enclose a function into its own lexical environment. **One of the ways you can do this is by wrapping it inside another function. You’ve now created a more specific, local scope.
Here’s an example:
function rocket(name, fc) {
let _name = name;
let _fuelConsumption = fc;
let _gas = 100;
let _miles = 0;
function fly() {
while (_gas > 0) {
_miles++;
_gas -= _fuelConsumption
};
console.log(`${_name} flew ${_miles} mile${_miles === 1 ? '' : 's'}.`);
};
return { fly };
};
let teslaRocket = rocket('Tesla Rocket', 1);
let sputnik = rocket('Sputnik', 1.5);
teslaRocket.fly();
// Tesla Rocket flew 100 miles.
sputnik.fly();
// Sputnik flew 67 miles.
Here we have a rocket
function, which has some local variables, and an inner fly
function. When I call rocket
, it returns a unique instance of an object with the fly
function stored inside that object (line 15). It’s important to note that I’m not actually calling fly
yet. I’m just returning an object that has the fly
function inside it.
Each time I call the rocket
function, I’m returning a separate instance of the fly
function, and that instance of fly
is actually holding on to its own instance of its lexical environment, even after rocket
has completed running.
That right there is the power of a closure: Each time I call rocket
, I’m creating an instance of fly
that has a persisting reference to a unique lexical environment.
You can see how this specific use case could be valuable if you’re trying to do something object oriented. We’ve encapsulated state and behavior in a persisting, reusable manner! And we didn’t use class
, constructor
, this
, or new
. How about that.
One other nice thing about doing object oriented programming in this manner is that your state is actually private and inaccessible compared to when you use class syntax from ES2015. You can’t access the name of a rocket with sputnik._name
(unless you added it to the returned object of rocket
on line 15, but you shouldn’t do that).
The power to keep private state truly inaccessible is a really good thing. It tells readers how the function should and should not be used. Keep private variables private. This also gives you the option to implement a true “read only” paradigm, if you wanted to.
For example:
function rocket(name, fc) {
let _name = name;
let _fuelConsumption = fc;
let _gas = 100;
let _miles = 0;
function fly() {
while (_gas > 0) {
_miles++;
_gas -= _fuelConsumption
};
console.log(`${_name} flew ${_miles} mile${_miles === 1 ? '' : 's'}.`);
};
return {
fly,
get name() {
return _name;
}
};
};
let teslaRocket = rocket('Tesla Rocket', 1);
let sputnik = rocket('Sputnik', 1.5);
teslaRocket.fly();
// Tesla Rocket flew 100 miles.
console.log(teslaRocket.name);
// Tesla Rocket
sputnik.fly();
// Sputnik flew 67 miles.
console.log(sputnik.name);
// Sputnik
I’ve added name access in the object returned by rocket
on lines 17–19, using get
syntax. I can now read the _name
property, but I don’t have the ability to change it. That’s exactly the kind of guardrail that I wanted to implement. Great success!
#closure #web-dev-bootcamps #code-camp #javascript #lexical-scope #visual studio code