JavaScript Concepts - Part 2
Understanding Hoisting
Hoisting refers to JavaScript's default behavior of moving declarations to the top of their scope during the compilation phase. This ensures that variables and function declarations are recognized before any code is executed.
Example with variables:
console.log(foo); // undefined
var foo = 1;
console.log(foo); // 1
- Function Declarations: The function body is hoisted.
- Function Expressions: Only the variable declaration is hoisted, not the function body.
// Function Declaration
console.log(foo); // [Function: foo]
foo(); // 'FOOOOO'
function foo() {
console.log('FOOOOO');
}
// Function Expression
console.log(bar); // undefined
bar(); // TypeError: bar is not a function
var bar = function () {
console.log('BARRRR');
};
For let
and const
, variables are hoisted but remain uninitialized, leading to a "temporal dead zone" until declared.
Closures Explained
Closures are functions that retain access to their outer scope's variables, even after the outer function has completed execution. This behavior is made possible due to JavaScript's lexical scoping.
Example:
function outerFunc() {
let outerVar = 'I am outside!';
function innerFunc() {
console.log(outerVar);
}
return innerFunc;
}
const myInnerFunc = outerFunc();
myInnerFunc(); // Output: "I am outside!"
The innerFunc
retains access to outerVar
even when called outside its lexical scope.
Rest vs Spread Operators
Rest Operator
The rest operator (...
) consolidates multiple elements into an array.
function myBio(firstName, lastName, ...details) {
return details;
}
myBio("John", "Doe", "Developer", "Male");
// Output: ["Developer", "Male"]
Spread Operator
The spread operator (...
) expands iterables into individual elements.
Example 1: Arrays
const nameParts = ["John", "Doe"];
const fullName = ["Mr.", ...nameParts];
console.log(fullName);
// Output: ["Mr.", "John", "Doe"]
Example 2: Strings
const word = "Hello";
console.log([...word]);
// Output: ["H", "e", "l", "l", "o"]
Example 3: Function Calls
const nums = [1, 2, 3];
console.log(Math.max(...nums)); // Output: 3
Example 4: Objects
const person = { firstName: "John", lastName: "Doe" };
const extended = { ...person, age: 30 };
console.log(extended);
// Output: { firstName: "John", lastName: "Doe", age: 30 }
Pure vs Impure Functions
- Pure Functions
- Predictable.
- No side effects.
- Always return the same output for the same input.
- Impure Functions
- Unpredictable.
- Have side effects (e.g., modifying global variables, API calls).
// Pure Function
const pureAdd = (arr, num) => [...arr, num];
// Impure Function
const impureAdd = (arr, num) => {
arr.push(num);
return arr;
};
Higher-Order Functions
A higher-order function accepts a function as an argument or returns one.
Map
const nums = [1, 2, 3];
const doubled = nums.map(num => num * 2);
console.log(doubled); // Output: [2, 4, 6]
Filter
const ages = [10, 20, 30];
const adults = ages.filter(age => age >= 18);
console.log(adults); // Output: [20, 30]
Reduce
const nums = [1, 2, 3];
const sum = nums.reduce((total, num) => total + num, 0);
console.log(sum); // Output: 6
Call, Apply, and Bind
These methods allow functions to execute in different contexts.
Call
Invokes a function with arguments passed individually.
function greet(greeting) {
return `${greeting}, ${this.name}`;
}
const person = { name: "John" };
console.log(greet.call(person, "Hello")); // Output: "Hello, John"
Apply
Similar to call
, but arguments are passed as an array.
console.log(greet.apply(person, ["Hi"])); // Output: "Hi, John"
Bind
Returns a new function with a specific context.
const boundGreet = greet.bind(person, "Hey");
console.log(boundGreet()); // Output: "Hey, John"
This detailed breakdown of JavaScript concepts provides a strong foundation for interviews and practical applications.