Closure in Javascript
In Javascript, a closure is a fundamental and powerful concept related to how functions work with their lexical environment.
Technically, all JavaScript functions are closures, but because most functions are invoked from the same scope that they were defined in, it normally doesn’t really matter that there is a closure involved. Closures become interesting when they are invoked from a different scope than the one they were defined in. This happens most commonly when a nested function object is returned from the function within which it was defined.
To understand closures, let's break down the components involved:
-
Functions: In JavaScript, functions are first-class citizens, meaning they can be assigned to variables, passed as arguments to other functions, and returned from other functions.
-
Lexical Environment: Each time a function is invoked, it creates its own lexical environment, which consists of its local variables, any variables from its outer (enclosing) scope, and a reference to the outer scope. The lexical environment determines the visibility and accessibility of variables within a function.
-
Scoping: Scope refers to the context in which variables are declared and can be accessed. In lexical scoping, the scope of a variable is determined by its location in the source code, specifically by where the variable is declared. When a variable is declared within a block of code, such as a function or a loop, its scope is limited to that specific block and any nested blocks within it. Variables declared outside of any blocks have a global scope and can be accessed from any part of the code. JavaScript uses function-level scope, which means that variables declared inside a function are only accessible within that function and not visible to the outside world.
Now, let's dive into closures:
Remember the fundamental rule of lexical scoping: JavaScript functions are executed using the scope they were defined in.
A closure is created when a function is defined within another function (outer function) and the inner function references variables from the outer function's lexical environment. This inner function "closes over" the variables it references, effectively capturing the state of the outer function at the time of its creation.
This, in a nutshell, is the surprising and powerful nature of closures: they capture the local variable (and parameter) bindings of the outer function within which they are defined.
Here's an example to illustrate closures in JavaScript:
function outerFunction() {
let outerVar = 'I am from the outer function';
function innerFunction() {
console.log(outerVar); // innerFunction has access to outerVar (closure)
}
return innerFunction; // Return the inner function from the outer function
}
const closureFunction = outerFunction();
closureFunction(); // Output: "I am from the outer function"
In this example, outerFunction
contains a variable outerVar
and an inner function innerFunction
. When outerFunction
is called and innerFunction
is returned, the variable outerVar
is still accessible to innerFunction
, even though outerFunction
has finished executing. This is because innerFunction
forms a closure over outerVar
, maintaining a reference to the lexical environment in which it was created.
It is perfectly possible for two or more nested functions to be defined within the same outer function and share the same scope.
function counter() {
let n = 0;
return {
count: function () {
return n++;
},
reset: function () {
n = 0;
},
};
}
let c = counter(), d = counter() // Create two counters
c.count() // => 0
d.count() // => 0: they count independently
c.reset() // reset() and count() methods share state
c.count() // => 0: because we reset c
d.count() // => 1: d was not reset
The counter() function returns a “counter” object. This object has two methods: count()
returns the next integer, and reset()
resets the internal state.
The first thing to understand is that the two methods share access to the private variable n. The second thing to understand is that each invocation of counter() creates a new scope — independent of the scopes used by previous invocations — and a new private variable within that scope.
So if you call counter()
twice, you get two counter objects with different private variables. Calling count()
or reset()
on one counter object has no effect on the other.
Closures are powerful because they allow you to create private variables and encapsulate data within functions. They also play a significant role in enabling concepts like module patterns and callbacks in JavaScript.