When a function is declared, variables in the context of its declaration are captured in its scope. For example, in the code below, the variable x
is bound to a value in the outer scope, and then the reference to x
is captured in the context of bar
:
var x = 4; // declaration in outer scope
function bar() {
console.log(x); // outer scope is captured on declaration
}
bar(); // prints 4 to console
Sample output:
4
This concept of "capturing" scope is interesting because we can use and modify variables from an outer scope even after the outer scope exits. For example, consider the following:
function foo() {
var x = 4; // declaration in outer scope
function bar() {
console.log(x); // outer scope is captured on declaration
}
return bar;
// x goes out of scope after foo returns
}
var barWithX = foo();
barWithX(); // we can still access x
Sample output:
4
In the above example, when foo
is called, its context is captured in the function bar
. So even after it returns, bar
can still access and modify the variable x
. The function foo
, whose context is captured in another function, is said to be a closure.
This lets us do some interesting things, such as defining "private" variables that are visible only to a specific function or set of functions. A contrived (but popular) example:
function makeCounter() {
var counter = 0;
return {
value: function () {
return counter;
},
increment: function () {
counter++;
}
};
}
var a = makeCounter();
var b = makeCounter();
a.increment();
console.log(a.value());
console.log(b.value());
Sample output:
1 0
When makeCounter()
is called, a snapshot of the context of that function is saved. All code inside makeCounter()
will use that snapshot in their execution. Two calls of makeCounter()
will thus create two different snapshots, with their own copy of counter
.
Closures are also used to prevent global namespace pollution, often through the use of immediately-invoked function expressions.
Immediately-invoked function expressions (or, perhaps more intuitively, self-executing anonymous functions) are essentially closures that are called right after declaration. The general idea with IIFE's is to invoke the side-effect of creating a separate context that is accessible only to the code within the IIFE.
Suppose we want to be able to reference jQuery
with $
. Consider the naive method, without using an IIFE:
var $ = jQuery;
// we've just polluted the global namespace by assigning window.$ to jQuery
In the following example, an IIFE is used to ensure that the $
is bound to jQuery
only in the context created by the closure:
(function ($) {
// $ is assigned to jQuery here
})(jQuery);
// but window.$ binding doesn't exist, so no pollution
See the canonical answer on Stackoverflow for more information on closures.