2

I'm using code blocks to simulate static function variables a la C. Here's the basic setup:

{
    let bob = 5;
    function b() {
        console.log(bob++);
    }
}

Now in chrome this compiles just fine with no complaints. In Safari however, I get a

SyntaxError: Unexpected identifier 'bob'. Expected a ':' following the property name 'let'.

I have no idea what's causing this discrepancy as both Chrome and Safari handle ECMAScript 6.

theEpsilon
  • 1,800
  • 17
  • 30
  • 1
    It would help if you'd post the *actual* code, and not a summary. – Pointy Aug 11 '20 at 16:46
  • 1
    as you have enclosed it into an object, all of those are now properties, so try: `{ bob: 5, b: () => { console.log(bob++) }` – Dominik Matis Aug 11 '20 at 16:48
  • 3
    @DominikMatis - No, freestanding blocks don't create objects. If the code above is as presented, it creates a freestanding block with code inside. – T.J. Crowder Aug 11 '20 at 16:49
  • @Pointy this is the actual (reduced) code that is failing. Try for yourself. – theEpsilon Aug 11 '20 at 16:51
  • So that's **all** of the context? Is it in a script loaded with a ` – Pointy Aug 11 '20 at 16:54
  • @Pointy literally copy and paste that in a developer console and it will fail with that error. – theEpsilon Aug 11 '20 at 16:55
  • No it won't because I'm running Firefox on Linux. – Pointy Aug 11 '20 at 16:57
  • 2
    Yep, Safari's bug – Dominik Matis Aug 11 '20 at 16:59
  • Putting a `function` declaration in a block like that is questionable, at best – Pointy Aug 11 '20 at 17:03
  • @Pointy - In loose mode, absolutely. it's well defined in strict mode (though even then it's a bit...odd...). – T.J. Crowder Aug 11 '20 at 17:14

2 Answers2

3

The problem is that the code is in loose mode and you're declaring a function in a block. Function declarations in blocks were only standardized in ES2015 and, well, they make sense in strict mode but in loose mode they're...weird.

In strict mode, your code works, possibly as you expect or possibly not. bob is accessible to b...and neither bob nor b accessible outside that block, unless you do something to expose them outside it.

Here's an example you can use to test it on both Safari and iOS Safari (I have only the latter available to me). This version gives an error:

<script>
window.onerror = e => {
    document.body.insertAdjacentText("beforeend", String(e));
};
</script>
<script>
{
    let bob = 5;
    function b() {
        document.body.insertAdjacentText("beforeend", bob++);
    }

    b();
}
</script>

The error is:

ReferenceError: Can't find variable: bob

This version works:

<script>
window.onerror = e => {
    document.body.insertAdjacentText("beforeend", String(e));
};
</script>
<script>
"use strict"; // <============================
{
    let bob = 5;
    function b() {
        document.body.insertAdjacentText("beforeend", bob++);
    }

    b();
}
</script>

I have also replicated the behavior in the latest version of JavaScriptCore (Apple's JavaScript engine), v265499. I installed¹ and ran it locally (changing console.log/insertAdjacentText to print, which is available in most of the raw engine executables) and get the same error in loose mode and not in strict mode.


If you want b available outside the block, you're probably better off doing something like this:

"use strict";
const b = (() => {
    let bob = 5;
    return function b() {
        console.log(bob++);
    };
})();
b();

Which is a lot bulkier, but... Alternatively, with let:

"use strict";
let b;
{
    let bob = 5;
    b = function b() {
        print(bob++);
    };
}
b();

¹ Using the very handy jsvu utility.

T.J. Crowder
  • 1,031,962
  • 187
  • 1,923
  • 1,875
  • https://stackoverflow.com/questions/49247362/safari-bug-with-block-scoped-variables - oh wait maybe not – Pointy Aug 11 '20 at 16:59
  • @Pointy - Ack! Function declarations in blocks! I'm such an idiot. – T.J. Crowder Aug 11 '20 at 17:00
  • Well yes, but the error that the OP reports is weird, if `function` placement is the problem, seems to me. – Pointy Aug 11 '20 at 17:03
  • @Pointy - Oh agreed. But the semantics of function declarations in blocks in loose mode are weird (to the extent they're defined; this may not match any of the limited number of common behaviors that could be standardized for loose mode). doubly so when you throw Annex B stuff at it. :-D I just feel like an idiot. It's not like there isnt' an entire section on them in my book! – T.J. Crowder Aug 11 '20 at 17:07
  • Oh great. Now I have to google what strict/loose mode is :D – theEpsilon Aug 11 '20 at 17:09
  • 1
    @theEpsilon - LOL Sorry -- https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Strict_mode. Basically, strict mode is a mode added to JavaScript in ES5 to bring some really, really problematic behaviors under control, and to make it possible to specify slightly different, more rational behaviors for code that opts into the new strict variant. You do that by adding `"use strict";` at the top of the compilation unit (loosely, "file") or the top of a function. Also note that JavaScript modules and `class` constructs are strict by default. Happy coding! – T.J. Crowder Aug 11 '20 at 17:13
1

Based on the error message, Safari is interpreting your block as an object literal. The syntax is valid, but Safari seems to be choking on it.

When I tried this out in my own console, I was able to get it working by placing a statement immediately following the block:

{ let bob = 5; function b(){ console.log(bob++); } } console.log('foo');

However, this seems to hoist the function out of the block scope without creating a closure, causing a ReferenceError claiming that it can't find variable bob.

I tried explicitly wrapping it in a function and starting that function with the use strict declaration, in case it was not running in strict mode as that can change the behavior:

function strict() {
  'use strict';
  {
    let bob = 5;
    function b() {
      console.log(bob++);
    }
    return b;
  } 
}
strict()();

And it worked!

TL:DR; block handling is weird in loose mode. Use strict mode when defining functions inside blocks.

gcochard
  • 11,408
  • 1
  • 26
  • 41
  • 1
    The function *should* be hoisted out of the block scope, because function declarations are scoped the old-fashioned way. – Pointy Aug 11 '20 at 17:05
  • My wording was bad. I've edited the answer to clarify that it should create a closure, but isn't. – gcochard Aug 11 '20 at 17:06