Really Understanding Javascript Closures
This post will explain in a simple way how Javascript Closures work. We will go over these topics and frequently asked questions:
- What is a Javascript Closure
- What is the reason behind the name ‘Closure’
- Actually viewing closures in a debugger
- how to reason about closures while coding
- the most common pitfalls of it’s use
A Simple Example (bug included)
The simplest way to understand closures is by realizing what problem they are trying to solve. Let’s take a simple code example with a counter being incremented 3 times inside a loop.
But inside the loop, something asynchronous is done with the counter. It could be that a server call was made, in this case let’s simply call setTimeout
that will defer it’s execution until a timeout occurs:
// define a function that increments a counter in a loop function closureExample() { var i = 0; for (i = 0; i< 3 ;i++) { setTimeout(function() { console.log('counter value is ' + i); }, 1000); } } // call the example function closureExample();
Some things to bear in mind:
- the variable i exists in the scope of the
closureExample
function and is not accessible externally - while looping through the variable the
console.log
statement is not immediately executed console/log
will be executed asynchronously 3 times, and only after each timeout of 1 second elapses- This means that 3 timeouts are set, and then the
closureExample
returns almost immediately
Which leads us to the main question about this code:
When the anonymous logging function gets executed, how can it have access to the variable ‘i’?
The question comes bearing in mind that:
- the variable i was not passed as an argument
- when the
console.log statement
gets executed, theclosureExample
function has long ended.
So What is a Closure then?
When the logging function is passed to the setTimeout
method, the Javascript engine detects that for the function to be executed in the future, a reference will be needed to variable i.
To solve this, the engine keeps a link to this variable for later use, and stores that link in a special function scoped execution context.
Such a function with ‘memory’ about the environment where it was created is simply known as: a Closure.
Why the name Closure then?
This is because the function inspects it’s environment and closes over the variables that it needs to remember for future use. The references to the variables are closed in a special data structure that can only be accessed by the Javascript runtime itself.
Is there any way to See the Closure?
The simplest way is to use the Chrome Developer Tools debugger, and set a breakpoint in line 7 of the code snippet above.
When the first timeout gets hit, the closure will show up in the Scope Variables panel of the debugger:
As we can see, the closure is just a simple data structure with links to the variables that the function needs to ‘remember’, in this case the i variable.
But then, where is the Pitfall?
We could expect that the execution log would show:
counter value is 0 counter value is 1 counter value is 2
But the real execution log is actually:
counter value is 3 counter value is 3 counter value is 3
This is not a bug, it’s the way closures work. The logging function is a closure (or has a closure, as the term is used in both ways) containing a reference to the i variable.
This is a reference, and not a copy, so what happens is:
- the loop finishes and the i variable value is 3
- only later will the first timeout expire, and the logging function will log the value 3
- the second timeout expires, and the logging function still logs 3, etc.
How to have a different counter value per async operation?
This can be done for example by creating a separate function to trigger the async operation. The following snippet would give the expected result:
function asyncOperation(counter) { setTimeout(function() { console.log('counter value is ' + counter); }, 1000); } function otherClosureExample() { var i = 0; for (i = 0; i < 3 ;i++) { asyncOperation(i); } } otherClosureExample();
This works because when calling asyncOperation
a copy is made of the counter value, and the logging will ‘close over’ that copied value. This means each invocation of the logging function will see a different variable with values 0, 1, 2.
Conclusion
Javascript closures are a powerful feature that is mostly transparent in the day to day use of the language.
They can be a convenient way to reduce the number of parameters passed to a function.
But mostly the fact that the closed variables are inaccessible to outside of the function makes closures a good way to achieve ‘private’ variables and encapsulation in Javascript.
Mostly the feature ‘just works’ and Javascript functions transparently remember any variables needed for future execution in a convenient way.
But beware of the pitfall: closures keep references and not copies (even of primitive values), so make sure that that is really the intended logic.
Reference: | Really Understanding Javascript Closures from our JCG partner Aleksey Novik at the The JHades Blog blog. |