2

I have been trying to understand how javascript works under the hood as deep as possible and have been trying to learn the spec simultaneously. I am a bit confused at the description of ForLoopEvaluation

According to the spec:

ForStatement : for ( LexicalDeclaration Expressionopt; Expressionopt ) Statement

  1. Let oldEnv be the running execution context's LexicalEnvironment.

  2. Let loopEnv be NewDeclarativeEnvironment(oldEnv).

  3. Let isConst be IsConstantDeclaration of LexicalDeclaration.

  4. Let boundNames be the BoundNames of LexicalDeclaration.

  5. For each element dn of boundNames, do

    a. If isConst is true, then i. Perform ! loopEnv.CreateImmutableBinding(dn, true).

    b. Else, i. Perform ! loopEnv.CreateMutableBinding(dn, false).

  6. Set the running execution context's LexicalEnvironment to loopEnv.

  7. Let forDcl be Completion(Evaluation of LexicalDeclaration).

  8. If forDcl is an abrupt completion, then

    a. Set the running execution context's LexicalEnvironment to oldEnv.

    b. Return ? forDcl.

  9. If isConst is false, let perIterationLets be boundNames; otherwise let perIterationLets be a new empty List.

  10. If the first Expression is present, let test be the first Expression; otherwise, let test be empty.

  11. If the second Expression is present, let increment be the second Expression; otherwise, let increment be empty.

  12. Let bodyResult be Completion(ForBodyEvaluation(test, increment, Statement, perIterationLets, labelSet)).

  13. Set the running execution context's LexicalEnvironment to oldEnv.

  14. Return ? bodyResult.

Now if we take a look at the ForBodyEvaluation

14.7.4.3 ForBodyEvaluation ( test, increment, stmt, perIterationBindings, labelSet )

The abstract operation ForBodyEvaluation takes arguments test (an Expression Parse Node or empty), increment (an Expression Parse Node or empty), stmt (a Statement Parse Node), perIterationBindings (a List of Strings), and labelSet (a List of Strings) and returns either a normal completion containing an ECMAScript language value or an abrupt completion. It performs the following steps when called:

  1. Let V be undefined.

  2. Perform ? CreatePerIterationEnvironment(perIterationBindings).

  3. Repeat,

    a. If test is not empty, then

    • Let testRef be ? Evaluation of test.
    • Let testValue be ? GetValue(testRef).
    • If ToBoolean(testValue) is false, return V.

    b. Let result be Completion(Evaluation of stmt).

    c. If LoopContinues(result, labelSet) is false, return ? UpdateEmpty(result, V).

    d. If result.[[Value]] is not empty, set V to result.[[Value]].

    e. Perform ? CreatePerIterationEnvironment(perIterationBindings).

    f. If increment is not empty, then i. Let incRef be ? Evaluation of increment. ii. Perform ? GetValue(incRef).

Now when we look at CreatePerIterationEnvironment, it has this to say:

14.7.4.4 CreatePerIterationEnvironment ( perIterationBindings )

The abstract operation CreatePerIterationEnvironment takes argument perIterationBindings (a List of Strings) and returns either a normal completion containing unused or a throw completion. It performs the following steps when called:

  1. If perIterationBindings has any elements, then

    a. Let lastIterationEnv be the running execution context's LexicalEnvironment.

    b. Let outer be lastIterationEnv.[[OuterEnv]].

    c. Assert: outer is not null.

    d. Let thisIterationEnv be NewDeclarativeEnvironment(outer).

    e. For each element bn of perIterationBindings, do

    • Perform ! thisIterationEnv.CreateMutableBinding(bn, false).
    • Let lastValue be ? lastIterationEnv.GetBindingValue(bn, true).
    • Perform ! thisIterationEnv.InitializeBinding(bn, lastValue).

    f. Set the running execution context's LexicalEnvironment to thisIterationEnv.

  2. Return unused.

I do not understand when a for loop is executed, since no function is being called a new execution context is not created. Why is it saying here for a normal for loop that if there are any perIterationBindings, that the lastIterationEnvironment should be the running execution context's LexicalEnvironnment?

I could be wrong but is this due to a possible use of the comma operator whereby a getter function is used? This is the only explanation I can come up with that would warrant the use of the term execution context being used in the context of a traditional for loop.

dbzx10299
  • 722
  • 2
  • 14
  • 1
    It's all in the spec - https://tc39.es/ecma262/#sec-returnifabrupt-shorthands unfortunately it's still not easy to understand! – phuzi Jun 08 '23 at 18:23
  • Have you tried digging around in JS implementations? V8, SpiderMonkey, JSCore, Hermes etc. if you know c++ I suggest you look there as its their job to follow the spec to a T – Asplund Jun 08 '23 at 18:50
  • There is always a running execution context, even if there is no function. After all the global code needs to be executed somehow too. – Felix Kling Jun 08 '23 at 21:02

1 Answers1

1

This has nothing to do with function calls. A for loop with lexical bindings (let, const) is creating a new scope for every iteration - see Explanation of `let` and block scoping with for loops for more details.

In the spec, lexical scoping is achieved by creating nested Declarative Environment Records. The innermost one that is active at a particular point during execution is always stored in the running execution context's LexicalEnvironment, it is simply changed whenever a scope is entered or left. It does not matter whether the execution context is the global one, was created from a function call, or something else.

Regarding lastIterationEnv, notice that in the first iteration of the loop (which includes the first evaluation of the test expression), the lastIterationEnv is the loopEnv that was set up in ForLoopEvaluation and in which the LexicalDeclaration statement was evaluated to initialise the loop variables. In the subsequent iterations (if there are any), the lastIterationEnv will be the PerIterationEnvironment of the previous iteration.

Bergi
  • 630,263
  • 148
  • 957
  • 1,375
  • I always viewed an execution context as a call stack frame, and when a for loop is executed, there is not a frame being added to the call stack. Am I right in saying that a for loop does create an execution context, but one that is not added to the call stack? – dbzx10299 Jun 09 '23 at 03:11
  • Yes, the execution context is a call stack frame. But no, a for loop does not create a new frame or add it to the call stack, it just *modifies* the frame that is at the top of the stack (notice the stack is never empty when code executes, there's always at least a global context). It only creates new environment records (scopes). – Bergi Jun 09 '23 at 03:14
  • The for statement on line 1 says "Let oldEnv be the running execution context's LexicalEnvironment", in this case it must be referring to the global execution context. That was the part I was confused about is although a for loop does not create a new execution context there is always the global execution context and that is the part that gets modified – dbzx10299 Jun 09 '23 at 03:19
  • Well if the `for` loop is inside a function, the running execution context at the time of the loop evaluation is that of the function call, not necessarily the global one. – Bergi Jun 09 '23 at 03:41