6

{function foo(){};foo=1;function foo(){};foo=2;}
console.log(foo); // 1

Can anyone explain why "1" is output here?

Edit: Seems there is an implementation difference, within "Chrome", "Firefox", "Nodejs" the output is "1", but within "Safari" output is "2"

Andre Geng
  • 61
  • 3
  • 1
    Does this answer your question? [What are the precise semantics of block-level functions in ES6?](https://stackoverflow.com/questions/31419897/what-are-the-precise-semantics-of-block-level-functions-in-es6) – ASDFGerte Sep 18 '20 at 03:15
  • Long story short, if you don't want to tread on very thin ice, "use strict"; – ASDFGerte Sep 18 '20 at 03:18
  • Yeah, it kind of related to this question. But what confused me is why after second function declaration, further reference to "foo" only refers to another block level variable "foo" instead of window.foo – Andre Geng Sep 18 '20 at 03:42

1 Answers1

1

Let's prettify the block and break it down:

{
  function foo() {};
  foo = 1;

  function foo() {};
  foo = 2;
}
console.log(foo); // 1

Block-scoped function declarations generally have their variable name hoisted out of their block (with a value of undefined), but only receive a value once inside the block. You can see more details on that behavior here.

But this situation is a bit different - you're declaring a function twice in a block. It looks like the first foo function gets assigned to the name that's visible outside the block. You can see this if you log the property descriptor on the window at different points in the code.

It looks like, once the duplicate function declaration is reached, further references to the foo variable name inside the block refer to a binding only inside the block. So, the foo = 2 at the bottom, since it's after the duplicate declaration, only results in the 2 value being bound to the foo name when referenced inside the block. The value outside the block remains the value that foo last held before the duplicate function declaration:

// Variable name is hoisted: it exists on the global object, but is undefined
console.log(Object.getOwnPropertyDescriptor(window, 'foo'));
{
  // Function declaration: global foo gets assigned to
  function foo() {};
  console.log(window.foo);
  // Assignment to foo name: global foo gets assigned to
  foo = 1;
  // Assignment to foo name: global foo gets assigned to
  foo = 3;
  console.log(window.foo);

  // Duplicate function declaration: past this point, foo now no longer refers to the global foo
  // but to a locally-scoped identifier
  function foo() {};
  // See how the window value remains at 3:
  console.log(window.foo);

  // So this assignemnt only changes the binding of the `foo` inside this block
  // while window.foo remains at 3:
  foo = 2;
}
console.log(foo); // 3

Another way of looking at the original code:

{
  function foo() {}; // this ALSO does: window.foo = function foo() {};
  foo = 1; // this ALSO does: window.foo = 1

  function foo() {};  // this actually does: fooLocal = function foo() {};
  // further references to "foo" in this block refer to fooLocal
  foo = 2; // this actually does: fooLocal = 2
}
console.log(foo); // references window.foo
CertainPerformance
  • 356,069
  • 52
  • 309
  • 320
  • I'm not sure this explanation works, as JS doesn't have block scope aside from let/const, IIUC. – Brian McCutchon Sep 18 '20 at 03:56
  • It's not *officially* block scope as is normally conceived, it just happens to be behavior that *results* in what looks like block scope once the duplicate declaration is reached. Similar block-level behavior can be seen in [Bergi's answer](https://stackoverflow.com/a/31461615) despite the fact that `const` and `let` aren't used. This appears to be *what* is happening, but it may well be undefined behavior. – CertainPerformance Sep 18 '20 at 04:00
  • @BrianMcCutchon [block scoped functions were introduced in ES6](http://es6-features.org/#BlockScopedFunctions). There are web compatibility semantics defined for browsers parsing JS source that happens to have something like OP's code but they are about as confusing and obtuse as the code that tries to mix multiple function declaration within the same block. – VLAZ Jul 08 '21 at 18:04