Deep Dive into JavaScript: Closures, Currying, and Function Composition

September 25, 2024 (2w ago)

Deep Dive into JavaScript: Closures, Currying, and Function Composition

JavaScript's functional programming capabilities can be greatly enhanced by mastering closures, currying, and function composition. These concepts make your code more reusable, modular, and efficient.

Closures

A closure is a function that retains access to variables from its containing scope, even after the outer function has finished executing. Closures are a fundamental concept in JavaScript, often used in scenarios where a function needs to remember its context.

function outerFunction(outerVariable) {
  return function innerFunction(innerVariable) {
    console.log(`Outer Variable: ${outerVariable}`);
    console.log(`Inner Variable: ${innerVariable}`);
  };
}
 
const newFunction = outerFunction("outside");
newFunction("inside"); // Output: Outer Variable: outside, Inner Variable: inside

Closures allow functions to maintain state and access their lexical environment, which can be very useful for building factories, event handlers, and more.

Currying

Currying transforms a function with multiple arguments into a series of nested functions, each taking one argument at a time. This helps create reusable, partially applied functions.

// Normal function
function multiply(a, b) {
  return a * b;
}
 
// Curried version of the function
function curriedMultiply(a) {
  return function(b) {
    return a * b;
  };
}
 
const multiplyByTwo = curriedMultiply(2);
console.log(multiplyByTwo(5)); // Output: 10

By breaking down a function's arguments, currying promotes reuse and makes functions more modular.

Function Composition

Function composition involves combining multiple functions together to form a new function, where the output of one function becomes the input of the next.

const add = (x) => x + 1;
const multiply = (x) => x * 2;
 
const compose = (f, g) => (x) => f(g(x));
 
const addThenMultiply = compose(multiply, add);
console.log(addThenMultiply(5)); // Output: 12

Here, the compose function takes two functions and returns a new function that applies g first, then f. This pattern is particularly useful when you need to combine many small, reusable functions to perform complex tasks.

Practical Example: Form Validation

Let’s put everything together with a practical example using closures, currying, and function composition for a form validation system.

// Closure for checking if input matches criteria
function isValidLength(length) {
  return function(input) {
    return input.length >= length;
  };
}
 
const isEmail = (input) => /\S+@\S+\.\S+/.test(input);
const hasUpperCase = (input) => /[A-Z]/.test(input);
 
// Compose validation functions
const validatePassword = compose(
  hasUpperCase,
  isValidLength(8)
);
 
console.log(validatePassword("Passw0rd")); // Output: true
console.log(isEmail("test@example.com")); // Output: true

In this case, we've composed several small functions into a larger, reusable function for validating inputs. The use of currying and closures makes it easy to extend and maintain these functions as the validation requirements grow.

Conclusion

Mastering closures, currying, and function composition in JavaScript allows for more expressive and concise code. These techniques help you break down complex problems into smaller, reusable pieces, improving both readability and maintainability.