Closures
Traditionally, closures have been a feature of purely functional programming languages. JavaScript shows its affinity with such functional programming languages by considering closures integral to the core language constructs. Closures are gaining popularity in mainstream JavaScript libraries and advanced production code because they let you simplify complex operations. You will hear experienced JavaScript programmers talking almost reverently about closures—as if they are some magical construct far beyond the reach of the intellect that common men possess. However, this is not so. When you study this concept, you will find closures to be very obvious, almost matter-of-fact. Till you reach closure enlightenment, I suggest you read and reread this chapter, research on the Internet, write code, and read JavaScript libraries to understand how closures behave—but do not give up.
The first realization that you must have is that closure is everywhere in JavaScript. It is not a hidden special part of the language.
Before we jump into the nitty-gritty, let's quickly refresh the lexical scope in JavaScript. We discussed in great detail how lexical scope is determined at the function level in JavaScript. Lexical scope essentially determines where and how all identifiers are declared and predicts how they will be looked up during execution.
In a nutshell, closure is the scope created when a function is declared that allows the function to access and manipulate variables that are external to this function. In other words, closures allow a function to access all the variables, as well as other functions, that are in scope when the function itself is declared.
Let's look at some example code to understand this definition:
var outer = 'I am outer'; //Define a value in global scope function outerFn() { //Declare a a function in global scope console.log(outer); } outerFn(); //prints - I am outer
Were you expecting something shiny? No, this is really the most ordinary case of a closure. We are declaring a variable in the global scope and declaring a function in the global scope. In the function, we are able to access the variable declared in the global scope—outer
. So essentially, the outer scope for the outerFn()
function is a closure and always available to outerFn()
. This is a good start but perhaps then you are not sure why this is such a great thing.
Let's make things a bit more complex:
var outer = 'Outer'; //Variable declared in global scope var copy; function outerFn(){ //Function declared in global scope var inner = 'Inner'; //Variable has function scope only, can not be //accessed from outside function innerFn(){ //Inner function within Outer function, //both global context and outer //context are available hence can access //'outer' and 'inner' console.log(outer); console.log(inner); } copy=innerFn; //Store reference to inner function, //because 'copy' itself is declared //in global context, it will be available //outside also } outerFn(); copy(); //Cant invoke innerFn() directly but can invoke via a //variable declared in global scope
Let's analyze the preceding example. In innerFn()
, the outer
variable is available as it's part of the global context. We're executing the inner function after the outer function has been executed via copying a reference to the function to a global reference variable, copy
. When innerFn()
executes, the scope in outerFn()
is gone and not visible at the point at which we're invoking the function through the copy variable. So shouldn't the following line fail?
console.log(inner);
Should the inner
variable be undefined? However, the output of the preceding code snippet is as follows:
"Outer" "Inner"
What phenomenon allows the inner
variable to still be available when we execute the inner function, long after the scope in which it was created has gone away? When we declared innerFn()
in outerFn()
, not only was the function declaration defined, but a closure was also created that encompasses not only the function declaration, but also all the variables that are in scope at the point of the declaration. When innerFn()
executes, even if it's executed after the scope in which it was declared goes away, it has access to the original scope in which it was declared through its closure.
Let's continue to expand this example to understand how far you can go with closures:
var outer='outer'; var copy; function outerFn() { var inner='inner'; function innerFn(param){ console.log(outer); console.log(inner); console.log(param); console.log(magic); } copy=innerFn; } console.log(magic); //ERROR: magic not defined var magic="Magic"; outerFn(); copy("copy");
In the preceding example, we have added a few more things. First, we added a parameter to innerFn()
—just to illustrate that parameters are also part of the closure. There are two important points that we want to highlight.
All variables in an outer scope are included even if they are declared after the function is declared. This makes it possible for the line, console.log(magic)
, in innerFn()
, to work.
However, the same line, console.log(magic)
, in the global scope will fail because even within the same scope, variables not yet defined cannot be referenced.
All these examples were intended to convey a few concepts that govern how closures work. Closures are a prominent feature in the JavaScript language and you can see them in most libraries.
Let's look at some popular patterns around closures.