Before JavaScript runs any code in a scope, it first scans for declarations and sets them up. This is called hoisting: it behaves as if declarations were lifted to the top of their scope. What you can do with a name before its line depends on how it was declared.
A function declaration is ready to use from the very first line of its scope:
console.log(square(6)); // 36
function square(n) {
return n * n;
}
A var name exists from the start, but its value is not assigned until its line runs. Before that, it is undefined:
console.log(level); // undefined (no error!)
var level = 5;
console.log(level); // 5
JavaScript treats that code as if it were written like this:
var level; // declaration hoisted
console.log(level); // undefined
level = 5; // assignment stays where it was
console.log(level); // 5
undefined is worse than an error. The program keeps running with a wrong value, and the bug shows up somewhere else later.
JavaScript hoists the declaration (var y), never the initialization (= 7). Compare these two:
var x = 5;
var y = 7;
console.log(x + " " + y); // both set first
var x = 5;
console.log(x + " " + y); // y used before its line
var y = 7;
In the second version, y exists (its declaration was hoisted), but its value 7 has not been assigned yet. That is why the rule is: declare your variables at the top of every scope. That is how JavaScript reads your code anyway.
let and const are hoisted too, but they are locked until their line runs. Touching them early throws an error. The stretch between the start of the scope and the declaration line is called the temporal dead zone:
console.log(score); // ReferenceError: Cannot access 'score' before initialization
let score = 10;
const has one extra rule: it must be given a value on the line it is declared. const carName; on its own is a SyntaxError: Missing initializer in const declaration, and because it is a SyntaxError, none of the script runs. (Some tutorials, including W3Schools, blame that error on using the const too early; the real cause is the missing = value.)
A function stored in a variable is only as hoisted as the variable holding it:
greetA(); // TypeError: greetA is not a function (var holds undefined)
var greetA = function() { return "hi"; };
greetB(); // ReferenceError: Cannot access 'greetB' before initialization
const greetB = () => "hi";
| Declaration | Used before its line |
|---|---|
| function name() {} | Works |
| var | undefined (silent) |
| let / const | ReferenceError |
| var fn = function... | TypeError when called |
| const fn = () => ... | ReferenceError |
| class Name {} | ReferenceError |
Declarations are hoisted to the top of their own scope, not the whole file. This can create surprising shadowing:
let theme = "dark";
function showTheme() {
console.log(theme); // ReferenceError, not "dark"!
let theme = "light";
}
showTheme();
The inner let theme is hoisted to the top of showTheme, so it shadows the outer one for the whole function, including the line before it. And that inner one is still in its dead zone.
const and let so any mistake throws an error instead of hiding. Then hoisting almost never matters, apart from the handy ability to call function declarations early.
What does this log?
console.log(total);
var total = 50;
undefined. The declaration var total is hoisted, but the value 50 is not assigned until its own line.What happens?
console.log(half(10));
const half = n => n / 2;
ReferenceError: Cannot access 'half' before initialization. An arrow function stored in const follows const rules: it is in the temporal dead zone until its line runs.Which one can safely be used on a line above its declaration?
let x = 1;const f = () => 1;function f() { return 1; }class C {}ReferenceError when used early.var to let and compare the two resultsconst and read the errorshowTheme example with its Try it Yourself button and explain in your notes why it fails