Understanding closures is fundamental to effective JavaScript programming. Here is my take on them.
Let us start with lexical scope first. In the following source fragment
which variables can be printed using the console.log statement?
1 | window.foo = 'foo'; |
Answer: we can print variables window.foo and bar but cannot print variable one.
Variable one is declared inside the function a and the console.log from the
outer scope of the function cannot access it.
1 | console.log(foo, bar); |
Call console.log cannot access variables inside a function,
unless the function surrounds console.log itself. Even multiple levels of nesting
are allowed
1 | function a() { |
Notice two levels of nestings: variable bar can be reached by console.log because
it belongs to the function b that surrounds the console.log statement.
Variable foo can be reached by the console.log because it belongs to function a
that also surrounds the console.log statement. Because this rule only looks at the way
the source code is written, it is called lexical scoping. Another word for surrounds
is closes, and thus the name for functions with variables reachable from inside: closures.
This principle becomes really useful when combined with another JavaScript language feature: functions can be passed as arguments to or returned from other functions. Whenever we pass a function around it can still access all variables outer to its lexical scope.
Let us take the previous example and instead of calling the inner function, return it.
1 | function a() { |
We have completely decoupled functions a and b, but they still follow lexical scope.
Thus whenever we execute f(), we are really executing b(). console.log thus can access
variable bar and foo. Even if no one else has a reference to the outer function a, and
it is meant to be garbage collected - the collector knows that there is an inner function with
access to foo and thus will not destroy it.
My favorite use of closures is to create private variables. For example, here is how we could implement a private increment-only counter.
1 | var counter = (function closure() { |
We are hiding actual value n inside a closure function, and return function inner.
The returned function has access to variable n due to lexical scope rule,
but anyone else trying to access n will cause a ReferenceError.