3

I apologize in advance for yet another closure question, but I'd like to clarify my understanding of the way closures are implemented in JavaScript.

Consider the following code:

01 'use strict';
02 function foo() {}
03 foo();

I have established in a question earlier in the year that a closure is conceptually (if not actually due to engine optimizations) created here.

And until foo is invoked on line 3 a corresponding execution context is not created.

So as far as I can tell from the spec, when evaluating this code:

  1. Each execution context has a "LexicalEnvironment" component used to resolve identifier references made by code within it (8.3, Table 23).
  2. FunctionCreate is called, passing in a reference (named "scope") to the "LexicalEnvironment" component of the current execution context. (14.1.19)
  3. FunctionCreate calls FunctionInitialize passing "scope" (9.2.5)
  4. FunctionInitialize ensures that the [[Environment]] internal slot of the function-object being created is set to the value of "scope" (the reference to the "LexicalEnvironment" component of the current execution context) (9.2.4)

Finally, when foo is actually invoked, I find the spec harder to interpret.

  1. In PrepareForOrdinaryCall, the "LexicalEnvironment" of the new execution context for the call is set to be the result of calling NewFunctionEnvironment (9.2.1.1)
  2. NewFunctionEnvironment copies the reference to the "LexicalEnvironment" component of the outer execution context (the [[Environment]] slot of the function-object) into the Environment record (EnvironmentRecord?) of the "LexicalEnvironment" component of the execution context under construction as the "outer lexical environment reference" (8.1.2.4)

Thus closures are implemented in a two step fashion:

  1. A linkage between function-object and the enclosing "LexicalEnvironment" of the enclosing execution context, is created at function-object instantiation time. This is the [[Environment]] internal slot of the function-object.
  2. When a function is invoked, this reference to the enclosing "LexicalEnvironment" component of the outer execution context (the contents of the [[Environment]] slot of the function object) is copied into a spec-imprecisely-defined(?)/EnvironmentRecord(?) sub-component of the "LexicalEnvironment" component of the new execution context.

Does this sound about right?

Ben Aston
  • 53,718
  • 65
  • 205
  • 331
  • There is nothing to close over, so where exactly do you see a closure? – a better oliver Nov 19 '15 at 11:38
  • 1
    @zeroflagL I direct you to the afore-linked question. http://stackoverflow.com/questions/28651512/will-a-simple-function-declaration-form-a-closure-in-javascript – Ben Aston Nov 19 '15 at 11:39
  • @zeroflagL strictly, yes, there's a closure, but the only variable in the environment closed over is the global object. – Alnitak Nov 19 '15 at 11:48
  • @Alnitak ...and global variables are not actually variables, but properties on the global object, so these are not present in the closure. I think. – Ben Aston Nov 19 '15 at 11:50
  • ok, but that global object presumably _is_ present? – Alnitak Nov 19 '15 at 11:51
  • @Alnitak - The "LexicalEnvironment" component of the global execution context, presumably is present. I am unsure if it will have anything in it, per my comment re global variables actually being properties. – Ben Aston Nov 19 '15 at 11:53
  • @Alnitak The function doesn't reference any variables, so it's conceptually not a closure. Moreover global variables are always present, so there is no need to close over them. – a better oliver Nov 19 '15 at 11:53
  • @zeroflagL This debate was had in the linked question above. According to the JavaScript specification the closure is present. This debate moves us into the realm of philosophy and whether "closure" refers to a concept or a runtime structure, which ultimately gets us no closer to an answer. According to the spec, the runtime structure is there. Also, see my comment re global variables not being variables and hence not closed over. – Ben Aston Nov 19 '15 at 11:56
  • First of all, the spec is very explicit about what can qualify as closure, and your example is not one. A closure requires a function to be created within another function, or being an arrow function. Secondly, what you are referring to are abstract operations. The engine is required to behave "as if", that's all. It's nothing real and no specific implementation is implied or required. It's merely a way to ensure consistent behavior across compliant engines. – a better oliver Nov 19 '15 at 13:21
  • Okay thanks for the explanation. Please can you supply a link to the relevant section of the spec where it says a closure is only present when a function is defined inside another (or being an arrow function)? – Ben Aston Nov 19 '15 at 13:50
  • 1
    FYI, the primary difference between the global environment and a normal function environment is that the global environment is "backed" by an object instead of some internal table. I.e., like you said, global variables become properties of the global object. However, it still behaves like any other environment. It is not special, it is just at the end of the environment chain. Global variables are not just magically accessible. And btw, `let`, `const` and `class`es when defined in global scope *don't* become properties of the global object. So yes, every function closes over the global env. – Felix Kling Nov 19 '15 at 14:51
  • Forget the "First of all" part of my last comment. The spec really seems to call _every_ function "closure". It doesn't mean that this is the one and only correct definition but I've learned something and will no longer object to that notion. – a better oliver Nov 19 '15 at 17:18
  • @zeroflagL Forgotten! Thanks for the update. – Ben Aston Nov 19 '15 at 17:36
  • Not sure why this question is tagged [tag:ES6]. Nothing has changed in the creation of closures since ES5, if maybe the names used in the spec. – Bergi Nov 19 '15 at 23:33
  • I wanted to use ES2015 terminology. – Ben Aston Nov 20 '15 at 08:06

1 Answers1

1

Does this sound about right?

Pretty much. I just wouldn't use the work "copied", rather "linked". To simplify it a little:

  1. When a function is created, it stores are reference to the environment it was created it.
  2. When the function is executed, this stored environment becomes the "outer environment" of the newly created function environment.

Or in pictures:

                  +----------------+                     +----------------+
   Function       |                |   [[Environment]]   |     Outer      |
   creation       |    Function    |-------------------->|  Environment   |
                  |                |                     |                |
                  +----------------+                     +----------------+
                                                                  ^        
                                                                  |        
                                                                  |        
                  +----------------+                              |        
   Function       |    Function    |  outer environment reference |        
   execution      |  Environment   |------------------------------+        
                  |                |                                       
                  +----------------+                                       

This happens to every function and depending on your definition of closure1, this makes every function a closure (or not).


1: I believe there are these two takes on what it means for a function to be a closure:

  • If a function stores a references to the environment it was created in, it's a closure (applies to every function in JS)
  • If a function stores a reference to the environment it was created in and "leaves" that environment (the environment "ceases to exist"), it's a closure. That's of course not always the case.
Felix Kling
  • 795,719
  • 175
  • 1,089
  • 1,143
  • Thank you. Am I correct in saying that the spec leaves **imprecise** the naming (and indeed location) of the "EnvironmentRecord" on the "LexicalEnvironment" of the execution-context for the current function invocation. I am talking about the memory location into which a link to the target of the [[Environment]] slot of the function-object is placed. – Ben Aston Nov 19 '15 at 15:07
  • I wouldn't say it's imprecise. The spec just defines defines the API and behavior. *How* exactly this API and behavior is implemented, is an implementation detail. Also note that the spec says *"A Lexical Environment is a specification type"* and http://www.ecma-international.org/ecma-262/6.0/#sec-ecmascript-specification-types . – Felix Kling Nov 19 '15 at 15:24
  • So the execution context looks like (pseudocode): `{ LexicalEnvironment: { OuterEnvironmentReference: , ... }, ... }`. If so, please can you confirm that the name of the property (here "OuterEnvironmentReference" is left to implementations by the spec) – Ben Aston Nov 19 '15 at 15:38