1

I'm a javascript newbie. I'm recently learning javascript and its great property closure.

But I'm confused by following code snippet.

function outerFn() {
  var outerVar = {};
  function innerFn() {
    alert('haha');
  }
  outerVar.pro = innerFn;
  return innerFn;
}

I think it is not a circular reference between innerFn and outerVar, because only outerVar pro points to the innerFn.

But some book points out that it is still a circular reference.

Could anyone explain whether it is a circular reference or not? Thanks in advance.

Pablo Lozano
  • 10,122
  • 2
  • 38
  • 59

1 Answers1

1

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;
}
Community
  • 1
  • 1
apsillers
  • 112,806
  • 17
  • 235
  • 239
  • 1
    Related information on possible optimisations: http://stackoverflow.com/a/8667141/1048572 – Bergi Oct 08 '14 at 15:52