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.