Javascript Scope Hoisting And Closures Complete Guide
Understanding the Core Concepts of JavaScript Scope, Hoisting, and Closures
JavaScript Scope, Hoisting, and Closures: A Comprehensive Guide
Types of Scope in JavaScript
Global Scope:
- Variables declared outside any function or block are in the global scope.
- These can be accessed from anywhere in the code.
- Example:
var globalVar = "I am Global"; function printGlobalVar() { console.log(globalVar); // Accessible here }
Function Scope:
- Variables declared inside a function using
var
are in the function’s local scope. - They are accessible only within that specific function.
- Example:
function printLocalVar() { var localVar = "I am Local"; console.log(localVar); // Accessible here } console.log(localVar); // Unaccessible here, will throw an error
- Variables declared inside a function using
Block Scope (ES6+):
- Introduced with
let
andconst
, block-scoped variables are only accessible within the{}
block they are declared in. - Example:
if (true) { let blockVar = "I am Block Scoped"; const blockConst = "I am also Block Scoped"; } console.log(blockVar); // Unaccessible here, will throw an error console.log(blockConst); // Unaccessible here, will throw an error
- Introduced with
Lexical Scope:
- Each function or block creates a new lexical environment and has its own scope.
- Functions can access variables in their parent scope, following a hierarchy up to the global scope but not vice versa.
- Example:
function outerFunction() { var outerVar = "I am Outer"; function innerFunction() { console.log(outerVar); // Accessible here } innerFunction(); } outerFunction(); console.log(outerVar); // Unaccessible here, will throw an error
Hoisting
Hoisting refers to the behavior of variable and function declarations being moved to the top of their respective scopes automatically by the JavaScript engine during the compilation phase. This can lead to some counterintuitive behaviors if not understood properly.
Variable Hoisting:
- Only declarations are hoisted, not initializations.
- Using
var
results inundefined
. Withlet
andconst
, it results in theTemporal Dead Zone (TDZ)
where the variable cannot be accessed until the declaration is encountered. - Example:
console.log(x); // undefined var x = 5; console.log(y); // ReferenceError: Cannot access 'y' before initialization let y = 5;
Function Hoisting:
- Function declarations are fully hoisted to the top of their scope.
- Function expressions are not hoisted completely like function declarations.
- Example:
greet(); // Outputs: "Hello!" function greet() { console.log("Hello!"); } sayHi(); // TypeError: sayHi is not a function var sayHi = function() { console.log("Hi There!"); };
Closures
A Closure is a term used to describe the environment in which an inner function is declared. It grants the inner function access to variables in its outer (enclosing) function's scope, as well as to global variables and functions, even after the outer function has finished execution. Closures are powerful constructs that make JavaScript a versatile language for both functional and object-oriented programming.
Creation of a Closure:
- When a function is defined inside another function and accesses variables from the outer function, a closure is created.
- The inner function retains a reference to the outer function's scope even when the outer function is complete.
- Example:
function createClosure(name) { return function() { console.log("Hello, " + name); }; } var greetAlice = createClosure("Alice"); greetAlice(); // Outputs: "Hello, Alice"
Practical Applications:
- Data hiding and encapsulation.
- Function factories.
- Private methods in classes.
- Asynchronous programming (callbacks, promise chains).
- Decorators.
- Caching/memoization.
Limitations / Considerations:
- Memory leaks: Not properly managing closures can result in memory leaks as the engine doesn't garbage collect the outer environment.
- Debugging challenges: Can make it harder to understand variable lifecycles.
- Performance considerations: Depending on the implementation, closures can carry performance costs.
Online Code run
Step-by-Step Guide: How to Implement JavaScript Scope, Hoisting, and Closures
JavaScript Scope
Scope in JavaScript defines where variables and functions are accessible. There are two main types of scope: Global Scope and Local Scope.
Example 1: Global Scope
// Declare a variable in the global scope
let username = "JohnDoe";
function printUsername() {
console.log(username); // Accessing global variable inside function
}
printUsername(); // Output: JohnDoe
console.log(username); // Output: JohnDoe -> Accessing global variable outside function
In this example, username
is declared outside any function, making it globally accessible from anywhere in the code, including within the printUsername
function.
Example 2: Local Scope
function greetUser() {
let message = "Hello, User!";
console.log(message); // Accessing local variable inside function
}
greetUser(); // Output: Hello, User!
console.log(message); // Error: Uncaught ReferenceError: message is not defined
// The 'message' variable cannot be accessed here because it's locally scoped.
In this example, message
is declared within the greetUser
function and thus only accessible within that function.
Nested Scopes:
Variables in an outer (parent) function can be accessed by inner functions.
function createUser() {
let name = "Alice";
function displayName() {
console.log(name); // Accessing parent's variable name
}
displayName(); // Output: Alice
}
createUser();
console.log(name); // Error: Uncaught ReferenceError: name is not defined
// The 'name' variable is not accessible here because it's in the createUser function's local scope.
JavaScript Hoisting
Hoisting refers to the default behavior of moving all declarations to the top of the current scope before code execution. Variable declarations and function declarations are hoisted, but not initializations.
Example 1: Hoisting with Variables
console.log(x); // Output: undefined
var x = 5;
console.log(x); // Output: 5
// Equivalent hoisting
var x;
console.log(x); // Output: undefined
x = 5;
console.log(x); // Output: 5
Here, the declaration var x;
is moved to the top of the script, even though technically it was declared later. That’s why we get undefined
when printing x
immediately after logging it before assigning its value.
Example 2: Hoisting with Functions
Functions are hoisted entirely in JavaScript.
helloWorld(); // Output: Hello, World!
function helloWorld() {
console.log("Hello, World!");
}
// Equivalent hoisting
function helloWorld() {
console.log("Hello, World!");
}
helloWorld(); // Output: Hello, World!
The entire function helloWorld
is hoisted to the top, allowing us to call it even before its definition in the code.
Hoisting with let
, const
, and class
:
let
andconst
declarations are hoisted but accessing variables before initialization within their block scope will result in a ReferenceError. This phenomenon is called the temporal dead zone (TDZ).class
declarations have similar behavior tolet
andconst
- you cannot access them before they've been declared.
// Using let
console.log(y); // ReferenceError: Cannot access 'y' before initialization
let y = 10;
console.log(y); // Output: 10
// Using const
console.log(z); // ReferenceError: Cannot access 'z' before initialization
const z = 20;
console.log(z); // Output: 20
// Using class
console.log(MyClass); // ReferenceError: Cannot access 'MyClass' before initialization
class MyClass {}
console.log(MyClass); // [Function: MyClass]
JavaScript Closures
A closure is a function having access to the parent scope, even after the parent function has closed. It “closes over” the variables available to it at the time of its creation.
Example 1: Basic Closure
function makeCounter() {
let count = 0; // Parent function scope variable
function counter() {
// Increment parent function's variable and return it.
count += 1;
return count;
}
return counter; // Returning the inner (child) function
}
const myCounter = makeCounter();
console.log(myCounter()); // Output: 1
console.log(myCounter()); // Output: 2
console.log(myCounter()); // Output: 3
// Even after the makeCounter() execution completes,
// myCounter holds the reference to the 'count' variable from parent function's scope.
Example 2: Practical Use of Closure (Private Data)
function createEmployee(name) {
let privateSalary = 0;
return {
setName: function(newName){
name = newName;
},
getName: function(){
return name;
},
getSalary: function(){
return privateSalary;
},
setSalary: function(salary){
privateSalary = salary;
},
calculateAnnualBonus: function(bonusPercentage){
return (privateSalary * bonusPercentage) / 100;
}
};
}
const emp = createEmployee('Bob');
emp.setSalary(50000);
console.log(emp.getSalary()); // Output: 50000
console.log(emp.calculateAnnualBonus(10)); // Output: 5000
emp.privateSalary = 75000; // Trying to directly manipulate private data
console.log(emp.getSalary()); // Still Output: 50000 -> Private data remains unchanged.
In this example, createEmployee
returns an object with some public methods (setName
, getName
, setSalary
, getSalary
, calculateAnnualBonus
). These methods can manipulate the private name
and privateSalary
variables from the createEmployee
function's scope even after createEmployee
itself is no longer running.
Summary
- Scope: Determines where variables and functions can be accessed; global or local.
- Hoisting: Moves variable and function declarations to the top of their scope before execution; initializations stay in place.
- Closures: A function remembers and retains access to variables from its containing (outer/parent) scope, even after it leaves the scope in which it was declared.
Top 10 Interview Questions & Answers on JavaScript Scope, Hoisting, and Closures
1. What is JavaScript Scope?
Answer: Scope in JavaScript refers to the visibility and lifetime of variables and functions. JavaScript has two main types of scope:
- Global Scope: Variables declared outside of a function are global and can be accessed from anywhere in the code.
- Local Scope: Variables declared inside a function are local and can only be accessed within that function.
2. What is Hoisting in JavaScript?
Answer: Hoisting is a JavaScript behavior where variable and function declarations are moved to the top of their containing scope during the compile phase, before the code is executed. This means that you can use a variable or function before it has been declared.
- Only declarations (e.g.,
var
,function
) are hoisted, not initializations. let
andconst
declarations are hoisted, but they remain uninitialized in a "temporal dead zone" until their declaration is processed.
3. What is the difference between var
, let
, and const
in terms of scope?
Answer:
var
: Function-scoped or globally-scoped. Variables declared withvar
can be redeclared and reassigned.let
: Block-scoped. Variables declared withlet
can be reassigned but not redeclared in the same scope.const
: Block-scoped and immutable (cannot be reassigned). However, if the variable references an object or array, the contents of that object or array can still be modified.
4. How does hoisting affect functions and function expressions?
Answer:
- Function Declarations: Entire function is hoisted. You can call the function both before and after its declaration.
- Function Expressions: Only the variable declaration is hoisted, not the function assignment. Invoking the function before the assignment will result in a TypeError.
5. What are closures in JavaScript?
Answer: A closure is a function that has access to its outer function's scope, even after the outer function has returned. This allows the inner function to "remember" and access variables from the outer function's scope, enabling data encapsulation and private state.
function createCounter() {
let count = 0;
return function incrementCounter() {
count++;
return count;
}
}
const counter = createCounter();
console.log(counter()); // 1
console.log(counter()); // 2
Here, incrementCounter
forms a closure that captures the count
variable.
6. Can you explain scope chain in JavaScript?
Answer: The Scope Chain is a system used by JavaScript to resolve variables and functions when a particular variable is referenced. When a variable is searched for, the search begins in the current scope and continues up to the global scope, following the scope chain. If the variable is not found, a ReferenceError is thrown.
7. Is it possible to create a closure unintentionally?
Answer: Yes, closures can be created unintentionally if an inner function references variables from its outer function's scope without the intent to create a private variable or function. Ensure that any function that references outer variables is intended to be a closure.
8. How does the this
keyword work with closures?
Answer: Inside a closure, the value of this
depends on the context in which the function is called, not where it is defined. If you need this
to point to a specific context, use techniques like .bind()
, .call()
, or arrow functions to ensure the correct context.
9. What are the benefits of using closures in JavaScript?
Answer: Closures provide several benefits:
- Encapsulation: Hide data and expose only necessary functionality (public methods).
- Data Privacy: Prevent direct modification of variables.
- Factory Functions: Create unique instances of functions with private state.
- Memory Management: Efficient use of resources by managing state without requiring global variables.
10. Can closures lead to memory leaks in JavaScript?
Answer: Yes, closures can potentially cause memory leaks if not managed carefully. When closures capture external variables, those variables are not garbage collected even if they are no longer needed. To prevent memory leaks, ensure that closures do not inadvertently hold references to objects that are no longer needed, and release those references when they are no longer required. Using modern JavaScript features and garbage collection best practices can mitigate this issue.
Login to post a comment.