During the compilation phase of JavaScript execution, the engine processes all declarations before running any actual code. This behavior, commonly referred to as hoisting, affects how variables and function are initialized and accessed within their respective scopes.
1. Variable Declaration Lifting
When a variable is declared using the var keyword, only the declaration is moved to the top of its scope. The initialization or assignment remains exactly where it was written. Consider the following snippet:
console.log(currentStatus);
var currentStatus = "initialized";
The engine interprets this as two separate steps:
var currentStatus;
console.log(currentStatus); // Outputs: undefined
currentStatus = "initialized";
Because the assignment hasn't occurred yet, accessing the variable returns undefined rather than throwing a reference error.
2. Function Declarations Versus Expressions
JavaScript handles named function declarations and function expressions differently during the hoisting phase. A named function declaration is fully lifted, meaning both the name and the function body are available before execution begins. In contrast, a function expression only lifts the variable declaration, leaving the function assignment in place.
var formatResult = function() {
console.log("Processed B");
};
function formatResult() {
console.log("Processed A");
}
formatResult();
The execution context reorders this as follows:
function formatResult() { // Full function declaration hoisted
console.log("Processed A");
}
var formatResult; // Variable declaration hoisted (ignored due to existing name)
formatResult = function() { // Assignment stays in original position
console.log("Processed B");
}
formatResult(); // Outputs: "Processed B"
Even though the declaration appears first in the transformed order, the runtime assignment overwrites the function reference, causing the expression's logic to execute.
3. Precedence Rules and Name Conflicts
When a variable and a function share the exact same identifier within a scope, function declarations take precedence during the hoisting phase. However, runtime assignments can still override the function reference.
console.log(computeValue);
console.log(computeValue());
var computeValue = 42;
function computeValue() {
console.log("Internal calculation");
}
computeValue = 85;
console.log(computeValue);
// computeValue(); // Throws TypeError: computeValue is not a function
The parser processes this in the following sequence:
function computeValue() { // Function declaration hoisted first
console.log("Internal calculation");
}
var computeValue; // Variable declaration hoisted (skipped due to existing function)
console.log(computeValue); // Prints the function definition
console.log(computeValue()); // Executes the function
computeValue = 42; // Runtime assignment overwrites the function
computeValue = 85; // Second assignment updates the value
console.log(computeValue); // Prints 85
This demonstrates that while the function binding wins during the compilation phase, subsequent assignments completely replace it, leading to type errors if invoked as a function later.
4. Local Scope Shadowing During Execution
Hoisting also impacts how variables are resolved within nested or immediate execution contexts. When a variable is declared inside a local scope, it is lifted to the top of that specific function, creating a local binding that shadows any outer variable with the same name, regardless of where the declaration appears.
var environmentMode = "production";
(function() {
console.log(environmentMode); // Outputs: undefined
var environmentMode = "development";
})();
Internally, the engine treats this as:
var environmentMode = "production";
(function() {
var environmentMode; // Local variable lifted to top of IIFE
console.log(environmentMode); // References local binding (undefined)
environmentMode = "development"; // Assignment occurs later
})();
Because the local declaration exists, the engine does not traverse the scope chain to find the global environmentMode. Instead, it uses the uninitialized local binding, resulting in undefined until the assignment statement executes.