Closures are functions that "remember" their lexical scope, even when the function is executed outside that scope. This allows powerful behavior such as data encapsulation.
Here’s an example of a closure:
function outerFunction(outerVariable) {
return function innerFunction(innerVariable) {
console.log("Outer Variable:", outerVariable);
console.log("Inner Variable:", innerVariable);
};
}
const closureExample = outerFunction("I am outside!");
closureExample("I am inside!");
Output: The closure remembers the outerVariable
even after the outer function has returned, allowing access to it inside innerFunction
.
Currying is the technique of transforming a function that takes multiple arguments into a sequence of functions each taking one argument. Partial application refers to fixing a specific number of arguments to create a new function.
function multiply(a) {
return function(b) {
return a * b;
};
}
const multiplyBy2 = multiply(2);
console.log(multiplyBy2(5)); // Output: 10
Output: The function is curried, meaning multiplyBy2
is a function that multiplies its argument by 2.
function multiply(a, b) {
return a * b;
}
function partiallyApplyMultiply(a) {
return function(b) {
return multiply(a, b);
};
}
const multiplyBy3 = partiallyApplyMultiply(3);
console.log(multiplyBy3(5)); // Output: 15
Output: Here, multiplyBy3
is a partially applied function that multiplies any given number by 3.
A higher-order function is a function that either takes one or more functions as arguments or returns a function. Higher-order functions are widely used in functional programming.
function mapArray(arr, transformFunction) {
const result = [];
for (let i = 0; i < arr.length; i++) {
result.push(transformFunction(arr[i]));
}
return result;
}
const numbers = [1, 2, 3, 4];
const doubled = mapArray(numbers, (num) => num * 2);
console.log(doubled); // Output: [2, 4, 6, 8]
Output: The mapArray
function is a higher-order function because it accepts a function as an argument, which it applies to each element of the array.
Functional programming (FP) emphasizes the use of pure functions, immutability, and first-class functions. Some important FP concepts include:
function add(a, b) {
return a + b;
}
console.log(add(2, 3)); // Output: 5
console.log(add(2, 3)); // Output: 5
Output: This function is pure because it always produces the same output for the same inputs.
const numbers = [1, 2, 3];
const newNumbers = [...numbers, 4]; // New array, original array is not mutated
console.log(newNumbers); // Output: [1, 2, 3, 4]
console.log(numbers); // Output: [1, 2, 3]
Output: The original array numbers
remains unchanged, demonstrating immutability.
JavaScript automatically handles memory management through garbage collection, which removes unused objects to free memory. However, memory leaks can occur when references to unused objects are still present.
let obj = { name: "JavaScript" };
obj = null; // Now the object is eligible for garbage collection
Output: The object { name: "JavaScript" }
is no longer referenced and will be cleaned up by the garbage collector.
Optimizing performance is crucial, especially for handling events like scrolling, resizing, or user input. Common techniques include debouncing and throttling.
let debounceTimer;
function debounce(func, delay) {
clearTimeout(debounceTimer);
debounceTimer = setTimeout(func, delay);
}
window.addEventListener('resize', () => {
debounce(() => {
console.log('Resized!');
}, 500);
});
Output: The "Resized!" message will only be logged once after the user has stopped resizing for 500 milliseconds.
let throttleTimer;
function throttle(func, limit) {
if (!throttleTimer) {
throttleTimer = setTimeout(() => {
func();
throttleTimer = null;
}, limit);
}
}
window.addEventListener('scroll', () => {
throttle(() => {
console.log('Scrolling!');
}, 1000);
});
Output: The "Scrolling!" message will only be logged once per second, no matter how many times the user scrolls.