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