Function declarations inside blocks are different from function declarations at the top level of a scope, because...they're in a block. :-) They were only recently (ES2015) and partially standardized; prior to that, supporting them was an allowed extension to the specification, but unfortunately different JavaScript engines supported them slightly differently (and sometimes their support varied over time), so what got standardized had to be the intersection of things that behaved basically the same way across the various implementations.
Your example is one of those that behaved the same way, though, so we can say what the spec says about it. The function isn't assigned to the test2
identifier until code execution enters the if
block. Here's your code:
function test() {
test2();
if (true) {
function test2() {
console.log(a);
}
let a = 4;
}
}
test();
...and here's roughly how it's handled in loose mode (there's a very minor difference in strict mode):
function test() {
var test2; // Identifier declared and initialized with `undefined` here
test2();
if (true) {
test2 = function test2() { // Function assigned here, at the top of the block
console.log(a);
};
let a = 4;
}
}
test();
As you can see, since the function hasn't been assigned to the test2
var yet, you can't call it where you have the call.
It's not clear from your code, but the assignment to the test2
identifier is hoisted to the top of the block where the function declaration appears. We can see that if we don't try to call it too early and log the value in test2
prior to the block (where it's undefined
) and inside the block above the declaration (where it's the function):
function test() {
console.log(test2); // undefined
// test2();
if (true) {
console.log(test2); // the function - assignment was hoisted
function test2() {
console.log(a);
}
let a = 4;
}
console.log(test2); // still available here!
}
test();
The slight difference in strict mode is primarily that the identifier has let
semantics instead of var
semantics and is only scoped to the block:
"use strict";
function test() {
// Can't use `test2` here at all
if (true) {
console.log(test2); // Now it exists and has been initialized with the function
function test2() {
console.log(a);
}
let a = 4;
}
// Can't use `test2` here at all
}
test();
I always recommend using strict mode, and this is one of the reasons. To me, it just makes sense that a function declared within a block is only accessible within that block.