In Javascript, when variables are defined in an outer scope, they’re accessible in any inner scope. When a function is defined, it forms a new scope. Variables defined in the outer scope are accessible inside, but not the reverse – variables defined in the inner scope are not visible outside it:
var a = 1;
function f() {
var b = 2;
console.log(a); // logs 1
}
console.log(b); // error
Function scopes can be nested inside other scopes. The scopes are defined by the source code and not at runtime in Javascript; this is called lexical scoping.
When you define a function it creates an inner scope, and the inner scope has access to the outer scope. The process of defining a function which has access to its outer scope variables is called creating a closure. A closure is a function with a reference to its execution context; or, a function bundled with the variables that were accessible to it when it was created.
In the above example, the function f
closes over the variable a
, meaning it keeps a reference to it. If the variable changes, the function can immediately use the updated value.
What if a function is returned from another function? The same rules apply:
function makeCounter() {
var count = 0;
return function() {
count += 1;
console.log(count);
};
}
var counter = makeCounter();
counter(); // logs 1
counter(); // logs 2
counter(); // logs 3
The function makeCounter
returned, but the anonymous function it returned forms a closure and keeps a reference to its context when it was created. It retains access to any local variables, here count
, that were in scope when it was created.
Even though the returned function assigned to counter
can access the local variable count
, it’s impossible to access otherwise:
console.log(counter.count); // undefined
What if the variable pointing to the function is dereferenced – won’t the garbage collector break things?
makeCounter = null; // no way to access the function now
console.log(makeCounter); // null
counter(); // logs 4
counter(); // logs 5
It still works. As long as the closure exists, the variables it refers to can’t be garbage collected.
Closures are how you make private data in Javascript.
Before the next example: putting ()
after a value means you’re trying to execute it. In the above example, the counter
variable was set equal to a function. Then it can be executed with ()
.
What if we put ()
right after a function definition, like this:
(function f() { console.log('iife'); })();
// we need the parentheses around the function
// definition - it won't work otherwise
This immediately logs iife
. Since this function was executed as soon as it was defined, it’s called an Immediately Invoked Function Expression, or IIFE.
IIFE’S are helpful when making objects or functions with access to private data, implemented using closures:
var obj = (function() {
var a = 1;
var b = 2;
return {
getA: function() {
console.log(a);
},
getB: function() {
console.log(b);
},
};
})();
Here, the getA
& getB
methods (not the returned object itself!) close over the variables a
and b
. The variables a
& b
now act as private data, accessible only by the methods in the returned object assigned to obj
.
What if you execute one of the methods in a different context with the same variable names?
var newCtx = {
a: 8,
b: 9,
};
obj.getA.call(newCtx); // still logs 1
Since scopes and closures are defined by lexical scoping rules and don’t change at run time, changing the context has no effect here.
In closing, closures are functions together with references to their surrounding context. This means that the function keeps access to any variables that were accessible to it when it was created. Closures are a way to make private data, since Javascript doesn’t have classes with private data members like some other languages do.