Skip to content

Instantly share code, notes, and snippets.

@raorao
Last active December 28, 2015 06:09
Show Gist options
  • Save raorao/7455054 to your computer and use it in GitHub Desktop.
Save raorao/7455054 to your computer and use it in GitHub Desktop.
Here's my attempt at explaining closures in JavaScript.
// As I've been know to yell, JavaScript is a stupid language, and I don’t mean that as an insult.
// For example, where Ruby has 50-ish enumerable methods to iterate over collections,
// JS has, basically, two. But that’s okay! That just means you have to build any complicated
// logical structures yourself in JavaScript, which makes your code more transparent and flexible.
// One important structure you often find yourself building from scratch is private functionality
// in object oriented programming. JavaScript closures are one way to achieve that goal.
var counter = (function() {
var currentCount = 0;
return {
increment: function() {
currentCount += 1
console.log("the counter is now set to " + currentCount)
}
}
})()
counter.increment() // returns "the counter is now set to 1"
counter.increment() // returns "the counter is now set to 2"
var currentCount = 100
counter.increment() // returns "the counter is now set to 3"
console.log(counter.currentCount) // undefined
console.log(currentCount) // returns "100"
//So wait, this is OO? where are the prototypes?
// "counter" returns an object that has no prototype. It's public interface is an
// object literal, but it has access to the private "currentCount" variable.
// This is an entirely distinct way to do object oriented programming in JS.
//So what is "counter", then?
// Well, its just a function, like any other in JavaScript.
// When called, it simply runs its code as written.
//So if its just a function, how can the internal function “increment” see the variable “currentCount”?
// When “increment” is defined in "counter", it immediately understands that “currentCount”
// is the thing defined inside the "counter" function. Even after "counter" is finished running,
// the object literal returned by "counter" knows to reference the "currentCount" value.
// That’s why we can call “increment” later and the variable "counter" isn’t reset to 0 every time.
//But we redefined "currentCount"! why doesn’t "counter" update its "currentCount" variable?
// This is why closures are so cool. “increment” knows to reference the variable "currentCount"
// in its environment, which points to a particular piece of data. even if we redefined the
// variable "currentCount" in the global scope, "increment" is still referencing the original value.
// That "increment" is a function being defined in one environment and run in another makes it a closure.
//What's up with the "counter" being wrapped in parantheses?
// That just makes "counter" run immediately when defined. These two examples would run identically:
var oneOff = function() { return "started from the bottom" }
oneOff(); // returns "started from the bottom"
var oneOff = ( function() { return "now we here" } )();
oneOff // returns "now we here"
// The only difference being that, if called multiple times, the first example would run multiple times.
// In the second example, oneOff would always reference the result of the first run.
// Anonymous functions don't necessarily have anything to do with closures,
// but are often convention when you are not using closures for distinct object instantiation.
// Wrapped in an anonymous function, "counter" is set to the returned object literal.
// Its also important to note that closures and OO aren’t necessarily related.
// We could have just as easily returned the "increment" function directly, and we could have
// also made "counter" a traditional function and used it for distinct object instantiation.
// Closures, for better or worse, are simply a way to manage state.
// Closures are also a helpful way to keep information private in JavaScript.
// As you can see, we can’t access the "currentCount" variable that was defined inside of "counter".
// I’ll leave you with a somewhat more complicated example of using closures and OO:
var createTree = function(type) {
var age = 0;
var height = 5;
var fruit = 0;
var ageOneYear = function() { age += 1 };
var growOneYear = function() { height += 1 };
var growFruit = function() { fruit += (age*2) };
var isAlive = function() { return (age < 40) };
return {
grow: function() {
if(isAlive()) {
ageOneYear();
growOneYear();
growFruit();
}
},
report: function() {
console.log("Your " + type + " tree is "
+ age + " years old, "
+ height + " feet tall, and has grown "
+ fruit + " " + type + "s.");
}
}
};
var OrangeTree = createTree('orange');
var AppleTree = createTree('apple');
OrangeTree.grow();
OrangeTree.report();
// "Your orange tree is 1 years old, 6 feet tall, and has grown 2 oranges."
OrangeTree.grow();
OrangeTree.report();
// "Your orange tree is 2 years old, 7 feet tall, and has grown 6 oranges."
AppleTree.grow();
AppleTree.report();
// "Your apple tree is 1 years old, 6 feet tall, and has grown 2 apples."
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment