0

In the specification, the runtime semantics for for-body evaluation in JavaScript are:

enter image description here

When running the following code, I expect two function-objects to be created, one per iteration of the for-body.

for(let x = 0; x < 2; x++) {
    function f() {}
}

Is this covered by 4.b. in the above specification snippet?

Ben Aston
  • 53,718
  • 65
  • 205
  • 331
  • 5
    Please link and/or quote the spec you're using, don't post a painting. – Bergi Feb 26 '20 at 14:20
  • 2
    "painting" lol! – Josh Wulf Feb 26 '20 at 14:35
  • 2
    Note that your screenshot is of 13.7.4.9, but you seem to be discussing 13.7.4.8. All the more reason to not use screenshots of text... – Heretic Monkey Feb 26 '20 at 14:37
  • I cannot see a good way to format text on SO with the complexity of the spec. If I have missed something please let me know. In this other question I painstakingly formatted a "code block", with poor results: https://stackoverflow.com/questions/60416842/does-this-safari-behavior-break-the-ecmascript-specification – Ben Aston Feb 26 '20 at 15:37

2 Answers2

1

Yes, indeed section 13.7.4.8 of the EcmaScript 2015 specification, point 4.b describes that the body of the for loop (the statement, as identified in 13.7.4.7) is evaluated, which in your example case means that the function object f is created.

This happens in each iteration (step 4).

You can spy on the double creation as follows:

let set = new Set;

for(let x = 0; x < 2; x++) {
    function f() {}
    set.add(f);
}

console.log(set.size); // 2 in Chrome, Firefox and Edge

I get output 2 in Chrome, FireFox and Edge. Some report 1 as output. This may well be a possible optimisation by the JavaScript engine.

In this context, mdn notes:

Functions can be conditionally declared, that is, a function statement can be nested within an if statement, however the results are inconsistent across implementations and therefore this pattern should not be used in production code. For conditional function creation, use function expressions instead.

This remark applies to loop constructs as well, since they also execute conditionally. So a more reliable result would be achieved with function expressions:

let set = new Set;

for(let x = 0; x < 2; x++) {
    var f = function f() {};
    set.add(f);
}

console.log(set.size); // 2 in Chrome, Firefox and Edge

Note that in both snippets, f is not scoped within the for body, but in the surrounding scope. So f is accessible after the loop has finished.

trincot
  • 317,000
  • 35
  • 244
  • 286
  • 1
    The result is 1 on my computer. There is an array in the set with 2 functions. set.entries().next().value.length is 2. – QuentinUK Feb 26 '20 at 14:52
  • @trincot And `4.e` is for the bindings at the top of the for loop. ie. `for () {}`. Would a better test in your example, be to assert that the two functions in the set are not the same object? – Ben Aston Feb 26 '20 at 14:58
  • But they are identical, set.entries().next().value[0] === set.entries().next().value[1] is true. – QuentinUK Feb 26 '20 at 14:58
  • 1
    @Ben, if the set has two entries, then that by definition means they are not the same, since a set cannot have duplicates. Now apparently some environments *do* produce the same function object (Quentin reports this), which might well be a JavaScript engine optimisation at work. I get 2 as output for this script. – trincot Feb 26 '20 at 14:59
  • @QuentinUK What host are you using (browser version/node version)? – Ben Aston Feb 26 '20 at 15:01
  • 1
    @Ben I am using Safari 11.1 – QuentinUK Feb 26 '20 at 15:01
  • 1
    @Ben you are right about the other comment: the bindings. I will correct that part in my answer. – trincot Feb 26 '20 at 15:02
  • I can confirm @QuentinUK's findings re Safari. – Ben Aston Feb 26 '20 at 15:03
  • I added some considerations to my answer. Would like to hear how this works on Safari. – trincot Feb 26 '20 at 15:34
  • 1
    I create a question for the Safari behavior. https://stackoverflow.com/questions/60416842/does-this-safari-behavior-break-the-ecmascript-specification – Ben Aston Feb 26 '20 at 15:34
0

Is this covered by 4.b. in the above specification snippet?

Not exactly. Point 4b in that snippet just says that the body is getting evaluated again during each iteration, regardless of what the body is.

The body itself is the statement

{
    function f() {}
}

and to see how that is evaluated we need to look at §13.2.13 Runtime Semantics: Evaluation of Block Statements, which does create the lexical environment for the body (which is nested inside the lexical environment with the variable x declared in the loop head) and runs the BlockDeclarationInstantiation, where the function is instantiated and bound to the name f.

Bergi
  • 630,263
  • 148
  • 957
  • 1,375