According to https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/function the behaviour of the function syntax in blocks is undefined or at least inconsistently implemented across browsers.
The block seems to affect where the function definition gets hoisted to. For example:
f = function() {return 2}
{
function f() {return 1}
}
console.log(f());
Outputs 1 because the later definition only seems to get hoisted to the top of its block. Whereas:
f = function() {return 2}
function f() {return 1}
console.log(f());
Outputs 2 because the second definition gets hoisted to the top, and therefore is the one that gets overwritten.
However, in strict mode the functions created by the statements are locally scoped, like you'd probably expect. So:
"use strict";
{
function f() {return 1}
}
console.log(f());
Throws a "f is not defined" error. While:
"use strict"
let f = function() {return 2} // The let is needed due to the strict mode
{
function f() {return 1}
}
console.log(f());
Now outputs 2 instead of 1, as the second function is a separate variable which now shadows the first function during the block, as opposed to changing it.
And lastly, I'm still not entirely sure what's happening with that last one. While:
{
function f() {return 1}
f = function() {return 2}
}
console.log(f());
Outputs 1 like you said, removing the block causes it to output 2 like you'd normally expect. And that's also the case if the block is only around the second definition.
However, I did find that using a function statement inside a block seems to create 2 variables, both with the same value, but one globally scoped and one locally scoped. This seems to be completely different to how variable assignments normally work, even when you use the older var keyword. So these all just either create a single local or global variable, but each will never produce both on its own:
{
// You'd only run one of these at a time by commenting all but 1 out
let a = 1; // Block
var a = 1; // Global
a = 1; // Global
this.a = 1; // Global
debugger;
}
And because there's a block scoped variable with the same name, it shadows the global inside the block. And so the second definition sets the block scoped variable instead of the global one. Which means that your second example doesn't work as expected.
The 1st only seems to work because the 3rd function definition seems to set this.f to match the block scoped f:
{
function f() {return 1}
f = function() {return 2}
debugger;
// ^ this.f is the last function, while the block scoped f is the second function
function f() {return 3}
// ^ This seems to make this.f match the block scoped f.
// Even though it shouldn't do anything here because it was hoisted up
debugger; // The values stay the same
}
debugger; // The values stay the same, but block scope is deleted
console.log(f()); // 2
So the somewhat satisfactory answer is that your second definitions in your snippets are block scoped, as opposed to globally scoped. On the other hand, the first definitions are simultaneously globally and block scoped (it makes 2 variables with the same value). The block scope value is the only one that gets directly overwritten by the second definition. And the reason the first snippet works is because apparently not all of the 3rd function gets hoisted. So after the second function definition, the 3rd function definition sets the value of the globally scoped function to the value of the block scoped function - for some reason. So that then means the second function gets called because that's the one in the global scope.
Well, that was interesting researching. Hope that helps. I'm assuming you want to know out of curiosity right? Because if you're relying on this behaviour, there's almost certainly a better way.