Closures are often viewed as an arcane art in the land of JavaScript. Once mastered, they allow you to write some truly amazing JavaScript. This article will get you up to speed on the magic of JavaScript closures.
What Is A Closure?
One of the key truths of JavaScript is that everything is an object. This, of course, includes functions.
A closure is nothing more than a function object with a related scope in which the function's variables are resolved.
Closures get their name because of the way they close over their contents. Consider the following bit of JavaScript:
topping = "anchovi"; function pizzaParty(numSlices) { var topping = "pepperoni", innerFunction = function() { var topping = "ham"; console.log(" .....But put " + topping + " on " + numSlices + " slices"); }; console.log("This pizza is all about the " + topping); innerFunction(); } pizzaParty(3);
If you open up your favorite console and run that bad boy, you will be greeted with a delicious message to the effect of "This pizza is all about the pepperoni ..... But put ham on 3 slices." This example illustrates some key concepts of JavaScript that are crucial to getting a hold on closures.
A Closure is a Function Object
How many function objects are in the above code? Well... we have our pizzaParty
function, and nested in that function is innerFunction
. Math hasn't always been my strong suit, but 1 + 1 = 2
in my book. Each function object has its own set of variables, which are resolved in each function's scope.
A Closure Has its Own Scope
Closures can't be fully understood without a firm grounding in scope. JavaScript's scope mechanism is what allows each function to have its own topping
variable, and without it we might have too much pepperoni, too little ham, or *gasp* ... some anchovies at our pizza party. Let's use a quick illustration to better illustrate this idea.
Functions are executed using the scope that was in effect when the function was defined. It has nothing to do with the scope in effect when the function is called.
Variable Accessibility Works Outside-In
The green arrows show that accessibility works from the outside in. Variables defined in the scope outside of a function are accessibile from within it.
If we were to omit the topping
variable from inside the pizzaParty
function, then we would get a message like "This pizza is all about the anchovi", but since pizzaParty
has a topping
variable within its own scope; those salty suckers will never get near our pizza party.
Likewise, the numSlices
parameter can be accessed from within innerFunction
because it is defined in the scope above - in this case the scope of pizzaParty
.
Variable Accessibility Does Not Work Inside-Out
The red arrows show that variables in scope for a function are never accessible outside of that function. This is the case only when a variable meets one of the following conditions:
- The
var
keyword is being used. - The variable is a parameter to the function or an outer function.
- The variable is a nested function.
Omitting the var
keyword when setting a variable will cause JavaScript to set the closest named variable in outer functions all the way up to the global scope. So, using our example, the ham topping
in innerFunction
cannot be accessed from pizzaParty
, and the pepperoni topping
in pizzaParty
cannot be accessed out in the global scope where the anchovi dwells.
JavaScript Uses Lexical Scoping
Lexical scope means functions are executed using the variable scope in effect when the function was defined. It has nothing to do with the scope in effect when the function is called. This fact is crucial to unlocking the power of closures.
Now that we understand what a closure is, and what scope means for closures, let's dive into some classic use cases.
Using Closures For Privacy
Closures are the way to conceal your code from the public eye. With closures, you can easily have private members that are shielded from the outside world:
(function(exports){ function myPrivateMultiplyFunction(num,num2) { return num * num2; } //equivalent to window.multiply = function(num1,num2) { ... exports.multiply = function(num1,num2) { console.log(myPrivateMultiplyFunction(num1,num2)); } })(window);
With closures, you can easily have private members that are shielded from the outside world.
Let's break it down. Our top level function object is an anonymous function:
(function(exports){ })(window);
We invoke this anonymous function right away. We pass it the global context (window
in this case) so we can "export" one public function, but hide everything else. Because the function myPrivateMultiplyFunction
is a nested function, it exists only within the scope of our closure; so we can use it anywhere inside this scope, and only in this scope.
JavaScript will hold a reference to our private function for use inside the multiply function, but myPrivateMultiplyFunction
cannot be accessed outside of the closure. Let's try this out:
multiply(2,6) // => 12 myPrivateMultiplyFunction(2,6) // => ReferenceError: myPrivateMultiplyFunction is not defined
The closure has allowed us to define a function for private use, while still allowing us to control what the rest of the world sees. What else can closures do?
Using Closures For Meta-Programming
Closures are quite handy when it comes to generating code. Tired of remembering all those pesky key codes for keyboard events? A common technique is to use a key map:
var KeyMap = { "Enter":13, "Shift":16, "Tab":9, "LeftArrow":37 };
Then, in our keyboard event, we want to check if a certain key was pressed:
var txtInput = document.getElementById('myTextInput'); txtInput.onkeypress = function(e) { var code = e.keyCode || e.which //usual fare for getting the pressed key if (code === KeyMap.Enter) { console.log(txtInput.value); } }
Capturing A Moment In Time
The above example isn't the worst, but we can use meta-programming and closures to make an even better solution. Using our existing KeyMap
object, we can generate some useful functions:
for (var key in KeyMap) { //access object with array accessor to set "dyanamic" function name KeyMap["is" + key] = (function(compare) { return function(ev) { var code = ev.keyCode || ev.which; return code === compare; } })(KeyMap[key]); }
Closures are so powerful because they can capture the local variable and parameter bindings of the function in which they are defined.
This loop generates an is
function for every key in KeyMap
, and our txtInput.onkeypress
function becomes a bit more readable:
var txtInput = document.getElementById('myTextInput'); txtInput.onkeypress = function(e) { if(KeyMap.isEnter(e)) { console.log(txtInput.value); } }
The magic starts here:
KeyMap["is" + key] = (function(compare){ })(KeyMap[key]); //invoke immediately and pass the current value at KeyMap[key]
As we loop over the keys in KeyMap
, we pass the value referenced by that key to the anonymous outer function and invoke it immediately. This binds that value to the compare
parameter of this function.
The closure we are interested in is the one we are returning from inside the anonymous function:
return function(ev) { var code = ev.keyCode || ev.which; return code === compare; }
Remember, functions are executed with the scope that was in place when they were defined. The compare
parameter is bound to the KeyMap
value that was in place during a loop iteration, and so our nested closure is able to capture it. We take a snapshot in time of the scope that was in a effect at that moment.
The functions we created allow us to skip setting up the code
variable everytime we want to check the key code, and we now have convenient, readable functions to use.
Using Closures To Extend The Language
At this point, it should be relatively easy to see that closures are vital to writing top notch JavaScript. Let's apply what we know about closures to augmenting one of JavaScript's native types (gasp!). With our focus on function objects, let's augment the native Function
type:
Function.prototype.cached = function() { var self = this, //"this" refers to the original function cache = {}; //our local, lexically scoped cache storage return function(args) { if(args in cache) return cache[args]; return cache[args] = self(args); }; };
This little gem allows any and every function to create a cached version of itself. You can see the function returns a function itself, so this enhancement can be applied and used like so:
Math.sin = Math.sin.cached(); Math.sin(1) // => 0.8414709848078965 Math.sin(1) // => 0.8414709848078965 this time pulled from cache
Notice the closure skills that come into play. We have a local cache
variable that is kept private and shielded from the outside world. This will prevent any tampering that might invalidate our cache.
The closure being returned has access to the outer function's bindings, and that means we are able to return a function with full access to the cache inside, as well as the original function! This small function can do wonders for performance. This particular extension is set up to handle one argument, but I would love to see your stab at a multiple argument cache function.
Closures in the Wild
As an added bonus, let's take a look at a couple uses of closures in the wild.
jQuery
Sometimes, the famous jQuery $
factory is not available (think WordPress), and we want to use it in the way we typically do. Rather than reach for jQuery.noConflict
, we can use a closure to allow functions inside to have access to our $
parameter binding.
(function($){ $(document).ready(function(){ //business as usual.... }); })(jQuery);
Backbone.js
On large Backbone.js projects, it might be favorable to have your application models private, and then expose one public API on your main application view. Using a closure, you can easily acheive this privacy.
(function(exports){ var Product = Backbone.Model.extend({ urlRoot: '/products', }); var ProductList = Backbone.Collection.extend({ url: '/products', model: Product }); var Products = new ProductList; var ShoppingCartView = Backbone.View.extend({ addProduct: function (product, opts) { return CartItems.create(product, opts); }, removeProduct: function (product, opts) { Products.remove(product, opts); }, getProduct: function (productId) { return Products.get(productId); }, getProducts: function () { return Products.models; } }); //export the main application view only exports.ShoppingCart = new ShoppingCartView; })(window);
Conclusion
A quick recap of what we learned:
- A closure is nothing more than a function object with a scope.
- Closures get their name by the way they "close" over their contents.
- Closures cash in big time on JavaScript's lexical scope.
- Closures are the way to achieve privacy in JavaScript.
- Closures are able to capture the local variable and parameter bindings of an outer function.
- JavaScript can be powerfully extended with some closure magic.
- Closures can be used with many of your favorite libraries to make them even cooler!
Thanks so much for reading! Feel free to ask any questions. Now let's enjoy the pizza party!
Comments