This could cause a circular reference within the JavaScript engine (because the parent lexical environment of innerFn
includes outerVal
, which includes innerFn
), but it does not cause a circular reference that can be observed by JavaScript code.
When outerFn
runs, the function innerFn
is defined. In JavaScript, a newly-defined function has access to all variables currently accessible in scope, so code inside of innerFn
can access outerVar
:
function outerFn() {
var outerVar = {};
function innerFn() {
alert(outerVar); // totally fine
}
return innerFn;
}
In ECMAScript terms, this is achieved because every function has a lexical environment used to resolve variable identifiers called [[Scope]]
. A newly-defined function's [[Scope]]
internal property is set to the lexical environment of its parent function. So, here, the [[Scope]]
of innerFn
is the lexical environment of outerFn
, which contains a reference to outerFn
.
In ECMAScript terms, the circular reference path goes:
innerFn
innerFn
's [[Scope]]
(a lexical environment)
innerFn
's [[Scope]]
's environment record
- the
outerVar
binding in innerFn
's [[Scope]]
's environment record
- the variable associated with the
outerVar
binding in innerFn
's [[Scope]]
's environment record
- this variable has
innerFn
as a property value
However, since you can't access a function's [[Scope]]
internal property from JavaScript code, you can't observe a circular reference from the code.
Bonus info
Note that a clever implementation will not actually store this circular reference in your code, because it sees that outerVar
is never used in any of outerFn
's child functions. The binding for outerVar
can be safely forgotten entirely when outerFn
ends. It is further interesting to note that this optimization is not possible with eval
, because it's not possible to recognize whether innerFn
will ever use outerVar
:
function outerFn() {
var outerVar = {};
function innerFn(codeStr) {
alert(eval(codeStr)); // will `codeStr` ever be "outerVar"?
}
return innerFn;
}