JavaScript is often a quirky language. It's crucial to understand concepts like hoisting, lexical scope, and the temporal dead zone (TDZ). Rather than just going through theory, let's dive into practical examples that illustrate these concepts, some of which might initially seem confusing.
Hoisting
Hoisting is JavaScript's behavior of moving declarations to the top of their containing scope during compilation. This can lead to some unexpected results.
Example 1: Variable Hoisting
console.log(myVar); // undefined
var myVar = 5;
console.log(myVar); // 5
In this example, you might expect a ReferenceError for the first console.log, but JavaScript "hoists" the declaration of In this example, you might expect a ReferenceError for the first console.log, but JavaScript "hoists" the declaration of myVar to the top, so it exists (but is undefined) before its initialisation. to the top, so it exists (but is undefined) before its initialisation.
Example 2: Function Hoisting
console.log(myFunction()); // "Hello, world!"
function myFunction() {
return "Hello, world!";
}
Function declarations are also hoisted, allowing them to be called before their definition in the code.
Example 3: Hoisting with Function Expressions
When a function is assigned to a variable using var, only the variable declaration is hoisted, not the function assignment. This can lead to different behavior compared to function declarations.
console.log(myFunction); // undefined
myFunction(); // TypeError: myFunction is not a function
var myFunction = function() {
console.log("Hello, world!");
};
myFunction(); // "Hello, world!"
The variable myFunction is hoisted and initialised to undefined, causing a TypeError when called before the function assignment.
Example 4: Hoisting Confusion with Var
function testHoisting() {
console.log(foo); // undefined
var foo = 'bar';
console.log(foo); // "bar"
}
testHoisting();
In testHoisting, foo is hoisted and initialised to undefined, resulting in the first console.log(foo) logging undefined.
Example 5: Hoisting with Let and Const
function testLetConst() {
console.log(bar); // ReferenceError
let bar = 'baz';
console.log(bar); // "baz"
}
testLetConst();
Variables declared with let and const are not hoisted to the top, so the first console.log(bar) results in a ReferenceError.
Lexical Scope
Lexical scope means that the scope of a variable is determined by its position in the source code.
Example 1: Nested Functions (Lexical Scope)
function outerFunction() {
var outerVar = 'I am outside!';
function innerFunction() {
console.log(outerVar); // "I am outside!"
}
innerFunction();
}
outerFunction();
Here, innerFunction can access outerVar because it is defined within the same lexical scope.
Example 2: Scope Confusion (Lexical Scope)
var x = 10;
function foo() {
var x = 20;
bar();
}
function bar() {
console.log(x); // 10
}
foo();
One might expect bar() to print 20, but it prints 10 because bar is defined in the global scope, where x is 10.
Example 3: Lexical Scope and Closures (Lexical Scope)
function createFunction() {
var localVar = 'local';
return function() {
console.log(localVar); // "local"
};
}
var myFunction = createFunction();
myFunction();
myFunction retains access to localVar even after createFunction has finished executing, demonstrating a closure
Example 4: Lexical Scope in Loops (Lexical Scope)
for (var i = 0; i < 3; i++) {
setTimeout(function() {
console.log(i); // 3, 3, 3
}, 1000);
}
for (let j = 0; j < 3; j++) {
setTimeout(function() {
console.log(j); // 0, 1, 2
}, 1000);
}
The var declaration in the first loop results in i being hoisted to the global scope, so all three setTimeout callbacks log 3. The let declaration in the second loop creates a new j for each iteration, so the callbacks log 0, 1, and 2.
Example 5: Comparing Lexical Scope for Function Declarations and Arrow Functions
I think this topic is big enough to complete in one example, so i created a article about it, here is the link
Temporal Dead Zone (TDZ)
The TDZ refers to the period between the entering of a scope and the actual declaration of variables, where they cannot be accessed.
Example 1: Let and Const - Temporal Dead Zone (TDZ)
console.log(a); // ReferenceError
let a = 5;
Unlike var, variables declared with let and const are not hoisted to the top of their block scope, resulting in a ReferenceError if accessed before their declaration.
Example 2 - Temporal Dead Zone (TDZ)
{
console.log(b); // ReferenceError
let b = 10;
}
{
let c = 20;
console.log(c); // 20
}
In the first block, accessing b before its declaration results in a ReferenceError due to the TDZ. In the second block, c is accessed after its declaration, so it logs 20.
Example 3: Temporal Dead Zone with Functions
{
console.log(func); // ReferenceError
const func = function() {
return 'Hello!';
};
}
{
const func = function() {
return 'Hello!';
};
console.log(func()); // "Hello!"
}
In the first block, accessing func before its declaration results in a ReferenceError due to the TDZ. In the second block, func is accessed after its declaration, so it works as expected.
Combining Concepts
Example 1: Hoisting and TDZ
{
console.log(d); // ReferenceError
let d = 15;
console.log(d); // 15
}
This example shows how accessing a let variable before its declaration results in a ReferenceError due to the TDZ, even though the declaration is hoisted to the top of the block.
Example 2: Lexical Scope and Hoisting
var e = 30;
function test() {
console.log(e); // undefined
var e = 40;
console.log(e); // 40
}
test();
console.log(e); // 30
In test(), e is hoisted and initialised to undefined within the function scope, so the first console.log(e) logs undefined.
Example 3: Hoisting, Lexical Scope, and TDZ Combined
let f = 50;
function outer() {
console.log(f); // 50
let f = 60;
inner();
function inner() {
console.log(f); // ReferenceError
}
}
outer();
Here, inner is defined within the scope of outer, but f is in the TDZ within outer before its declaration, leading to a ReferenceError when inner tries to access it.
Conclusion
Understanding hoisting, lexical scope, and the temporal dead zone is vital for writing robust JavaScript code. These examples illustrate how JavaScript handles variable and function declarations, scopes, and the strange behaviour of accessing variables before their declaration. By experimenting with these concepts, you'll develop a more intuitive grasp of JavaScript's behaviour.
Related Article By Author:
- Node.js vs. Browser: Understanding the Global Scope Battle
- Comparing Lexical Scope for Function Declarations and Arrow Functions
References
MDN Web Docs: Hoisting
MDN Web Docs: Closures
MDN Web Docs: Lexical Scoping
MDN Web Docs: Temporal Dead Zone
JavaScript Info: Variable Scope, Closure
Eloquent JavaScript: Functions
Top comments (0)