Understanding JavaScript Hoisting: var, let, const and Function Declarations
This article explains how JavaScript engines hoist function and variable declarations, compares the behavior of var, let, and const during creation, initialization, and assignment, and demonstrates the resulting runtime errors and output through clear code examples.
Key Points
Function hoisting
Differences in hoisting for var, let, and const
JavaScript performs a preprocessing step before executing code, moving variable and function definitions (creation, initialization, assignment) to the top of their scope. The exact behavior varies slightly between engines.
Code Example 1 (function declaration hoisted):
<code>// foo调用在定义之前
foo();
function foo() {
console.log('hello world');
}
// hello world</code>Code Example 2 (var assigned a function, not hoisted):
<code>// 报错
foo();
var foo = function () {
console.log('hello world');
};
// TypeError: foo is not a function</code>Code Example 3 (var hoisted, initialization not):
<code>// 不报错
console.log(`a is ${a}`);
var a = 'hello world';
// a is undefined</code>Code Example 4 (let hoisted, initialization not):
<code>// 报错
console.log(`a is ${a}`);
let a = 'hello world';
// ReferenceError: a is not defined</code>Code Example 5 (const hoisted, no assignment phase):
<code>// 报错
console.log(`a is ${a}`);
const a = 'hello world';
// ReferenceError: a is not defined</code>The three stages of creation, initialization, and assignment are treated differently:
Function definitions have all three stages hoisted.
var declarations have creation and initialization hoisted; assignment is not.
let declarations have creation hoisted only; initialization and assignment are not.
const declarations have creation hoisted; initialization is not hoisted and there is no separate assignment phase.
Detailed walkthrough of the examples:
In code1, the function is created, initialized to undefined, then assigned the function body before the call, so the call succeeds.
In code2, var creates foo and initializes it to undefined, then the call occurs before the assignment, causing a TypeError.
Code3 behaves like code2: the variable is initialized to undefined, so the logged value is undefined.
In code4, let creates the binding but does not initialize it; the console.log runs before initialization, throwing a ReferenceError.
Code5 follows the same pattern as code4.
Hoisting also respects a priority order. Consider the following code:
<code>console.log(foo);
function foo() {
console.log('hello world');
}
var foo = 1;
console.log(foo);
// [Function: foo]
// 1</code>The engine first creates the var binding (foo = undefined), then processes the function declaration, overwriting the binding with the function object, then executes the first log (showing the function). Later the var assignment runs, changing foo to 1, so the second log outputs 1. Replacing var with let would raise a SyntaxError because let does not allow a second creation.
Another interesting let behavior:
<code>let x = x; // x之前未定义过
// Uncaught ReferenceError: Cannot access 'x' before initialization
x = 1;
// Uncaught ReferenceError: x is not defined</code>Here, let creates the binding, but the right‑hand side reference occurs before initialization, triggering a ReferenceError and leaving the binding in the temporal dead zone, so subsequent accesses also fail.
ES6 class declarations are also hoisted similarly to let/const; referencing a class before its declaration throws an exception.
Best practice: Declare before you use.
MaoDou Frontend Team
Open-source, innovative, collaborative, win‑win – sharing frontend tech and shaping its future.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.