From the previous article, we know that a variable is a property of the LexicalEnvironment
object.
Here we discuss access to outer variables and nested functions. In-depth understanding of closures follows automatically.
Access to outer variables
What if a variable is accessed, but it isn’t local? Like here:
In this case, the the interpreter finds the variable in the outer LexicalEnvironment
object.
The process consists of two steps:
- First, when a function
f
is created, it is not created in an empty space.
There is a current LexicalEnvironment
object. In the case above, it’s window
(a
is undefined
at the time of function creation).
When a function is created, it gets a hidden property, named [[Scope]]
, which references currentLexicalEnvironment
.
- Later, when the function runs, it creates it’s own
LexicalEnvironment
and links it with [[Scope]]
.
So when a variable is not found in the local LexicalEnvironment
, it is searched outside:
If a variable is read, but can not be found anywhere, the error is generated.
Certain language constructs block the error, for example typeof x
works if there is no x
(and returnsundefined
), but that’s an exception.
If a variable is set, but not found anywhere, then it is created in the outmost LexicalEnvironment
, which iswindow
.
Nested functions
Functions can be nested one inside another, forming a chain of LexicalEnvironments
which can also be called a scope chain.
LexicalEnvironments
form a chain (from inside out):
So, function
g
has access to
g, a
and
f
.
Closures
Nested function may continue to live after the outer function has finished:
3 |
this .say = function (phrase) { |
4 |
alert(name + ‘ says: ‘ + phrase) |
9 |
var user = new User( ‘John‘ ) |
Marking up LexicalEnvironments
:
Note, the this
context is not related to scopes and variables. It does not participate here.
As we see, this.say
is a property in the user
object, so it continues to live after User
completed.
And if you remember, when this.say
is created, it (as every function) gets an internal referencethis.say.[[Scope]]
to current LexicalEnvironment
. So, the LexicalEnvironment
of the current User
execution stays in memory. All variables of User
also are it’s properties, so they are also carefully kept, not junked as usually.
The whole point is to ensure that if the inner function wants to access an outer variable in the future, it is able to do so.
- The inner function keeps a reference to the outer
LexicalEnvironment
.
- The inner function may access variables from it any time even if the outer function is finished.
- The browser keeps the
LexicalEnvironment
and all it’s properties(variables) in memory until there is an inner function which references it.
This is called a closure.
Mutability of LexicalEnvironment
Several function may share same outer LexicalEnvironment
. In this case they can modify it’s properties.
In the example below, this.fixName
changes name
, which is used by this.say
:
03 |
this .fixName = function () { |
04 |
name = ‘Mr.‘ + name.toUpperCase() |
07 |
this .say = function (phrase) { |
08 |
alert(name + ‘ says: ‘ + phrase) |
13 |
var user = new User( ‘John‘ ) |
17 |
user.say( "I‘m alive!" ) |
Here user.fixName.[[Scope]]
and user.say.[[Scope]]
reference same LexicalEnvironment
, which corresponds to new User
run.
From (1) to (2), the LexicalEnvironment.name
is updated, so both functions see the variable change.
Variables in outer LexicalEnvironment
may change.
Inner functions always see the last value.
The notorious closure loop
The task below contains interesting tricks, best demonstrated by an example. Please, glance at the solution, or, much better, try to solve it.
Note that the shooter
function. Does not have a variable named i
.
So, when it is called, the interpreter takes i
from the outer LexicalEnvironment
.
The problem is that at the time when shooters are run, function makeArmy
has already finished the execution.
The loop has finished and the variable i
is now 10
.
There are two possible solutions.
The first one is to put the correct value into the shooting function itself.
05 |
for ( var i=0; i<10; i++) { |
07 |
var shooter = function () { |
08 |
alert( arguments.callee.i ) |
12 |
shooters.push(shooter) |
Another, more advanced solution is to use an extra function to trap the current value of i
:
05 |
for ( var i=0; i<10; i++) { |
07 |
var shooter = function (i) { |
15 |
shooters.push(shooter) |
Let’s consider the highlighted fragment more thoroughly.
1 |
var shooter = function (i) { |
Here, the actual shooting function is created as the result of an anonymous function(i)
which is created and executed in one place.
So, when it comes to executing alert(i)
, it will be taken from LexicalEnvironment
of the anonymous function.
So, the anonymous function traps current i
into it’s LexicalEnvironment
and allows the shooter to access it.
The last way is to wrap the whole loop into temporary function. Sometimes that’s more readable:
05 |
for ( var i=0; i<10; i++) ( function (i) { |
07 |
var shooter = function () { |
11 |
shooters.push(shooter) |
The (function(i) { ... })
definition is wrapped into brackets to make sure the interpreter treats that as expression.
[[Scope]]
for new Function
There is an exception to general scope binding rule. When you create a function using new Function
, it’s[[Scope]]
points to window
, not to current LexicalEnvironment
.
The following example demonstrates how a function, created with new Function
ignores local variable a
and outputs the global variable.
The regular behavior:
05 |
var func = function () { alert(a) } |
And now the function, created by new Function
:
5 |
var func = new Function( ‘‘ , ‘alert(a)‘ ) |
http://javascript.info/tutorial/closures
Summary
We discussed the following topics:
- How variables are handled in JavaScript.
- How scopes work.
- What is a closure and how to use it.
- Possible pitfalls and subtles in working with closures.
Closures in JavaScript is like a salt. You can live without it, but not very long. Usually people put it everywhere…