Hoisting in JavaScript:
Hoisting is a JavaScript mechanism where variables and function declarations are moved ("hoisted") to the top of their scope (global or function scope) before the code execution. This means that regardless of where variables and functions are declared in the code, they are treated as if they are at the top of their scope.
1. Hoisting of Variables:
Variables declared with var
are hoisted to the top, but their initial values are not hoisted. They are initialized with undefined
. This is why accessing a var
variable before its declaration does not throw an error, but results in undefined
.
Example:
console.log(x); // Output: undefined
var x = 10;
What happens internally:
var x; // Declaration hoisted to the top
console.log(x); // Output: undefined
x = 10; // Assignment happens where it is in the code
let
andconst
variables are hoisted too, but they are not initialized withundefined
. Instead, they enter a "temporal dead zone" from the start of the block until they are declared. Accessing them before their declaration results in aReferenceError
.
Example:
console.log(a); // ReferenceError
let a = 5;
2. Hoisting of Functions:
Function declarations are fully hoisted, meaning both the declaration and the function body are moved to the top of their scope. This allows you to call a function before you define it in the code.
Example:
myFunction(); // Output: "Hello"
function myFunction() {
console.log("Hello");
}
What happens internally:
function myFunction() {
console.log("Hello");
}
myFunction(); // Output: "Hello"
However, function expressions (functions assigned to a variable) are hoisted differently. Only the variable declaration is hoisted, not the function itself. So calling a function expression before the variable assignment will result in undefined
or a TypeError
.
Example:
console.log(sayHello); // Output: undefined
var sayHello = function() {
console.log("Hi");
};
This behaves as:
var sayHello; // Variable declaration hoisted
console.log(sayHello); // Output: undefined
sayHello = function() {
console.log("Hi");
};
In case of let
and const
:
console.log(sayHello); // ReferenceError
let sayHello = function() {
console.log("Hi");
};
Closures in JavaScript:
Closures are a fundamental concept in JavaScript where an inner function has access to variables from its outer (enclosing) function, even after the outer function has finished execution. The closure "remembers" the variables and can use them later.
A closure gives an inner function access to three scopes:
Its own scope (variables defined within it).
The outer functionโs scope (variables defined in the outer function).
The global scope.
1. Basic Example of Closure:
function outerFunction() {
let outerVariable = "I'm from the outer scope";
function innerFunction() {
console.log(outerVariable);
// Accessing a variable from the outer scope
}
return innerFunction;
}
const closureFunc = outerFunction();
closureFunc(); // Output: "I'm from the outer scope"
Here, innerFunction
is a closure because it has access to outerVariable
even after outerFunction
has finished execution.
2. Practical Use of Closures:
Data Privacy: Closures are often used to emulate private variables in JavaScript. Since the inner function can still access the outer function's variables, it can be used to create data that is private and can only be accessed through a specific function.
Example:
function counter() {
let count = 0;
return function() {
count += 1;
return count;
};
}
const increment = counter();
console.log(increment()); // Output: 1
console.log(increment()); // Output: 2
console.log(increment()); // Output: 3
In this example, count
is not directly accessible from the outside but can be modified by the returned function.
3. Closures and Loops (Classic Pitfall):
A common problem with closures is when they are created inside loops, leading to unexpected results due to how JavaScript handles closures and variable scoping.
Example of a common mistake:
for (var i = 1; i <= 3; i++) {
setTimeout(function() {
console.log(i);
}, 1000);
}
Output:
Copy code4
4
4
The reason is that setTimeout
creates a closure that refers to the same i
variable in memory, and by the time the setTimeout
callback runs, i
has already become 4
.
Fix using an IIFE (Immediately Invoked Function Expression):
for (var i = 1; i <= 3; i++) {
(function(i) {
setTimeout(function() {
console.log(i);
}, 1000);
})(i);
}
Output:
Copy code1
2
3
By using an IIFE, a new scope is created for each loop iteration, preserving the correct value of i
.
Alternatively, using let
solves this issue:
for (let i = 1; i <= 3; i++) {
setTimeout(function() {
console.log(i);
}, 1000);
}