This repository contains examples and explanations of advanced JavaScript functions designed to enhance code quality, performance, and maintainability. These functions cover various techniques such as debouncing, throttling, memoization, and functional programming concepts like currying and composition.
JavaScript offers a rich set of features and libraries that allow developers to write efficient, scalable, and maintainable code. This document explores advanced JavaScript functions that can significantly improve the quality of your code by reducing complexity, enhancing performance, and promoting better coding practices.
Debouncing is a technique used to ensure that a function does not execute until after a certain amount of time has passed since the last time it was invoked. This is particularly useful for events that trigger frequently, such as window resizing or scrolling.
function debounce(func, delay) {
let timer
return (...args) => {
clearTimeout(timer)
timer = setTimeout(() => {
func.apply(this, args)
}, delay)
}
}
Usage Example
function someFunction(arg) {
console.log(arg)
}
const debouncedFunction = debounce(someFunction, 1000)
// This will only call someFunction once after 1000ms of inactivity
debouncedFunction('Hello') // Call 1
debouncedFunction('World') // Call 2 (ignored)
debouncedFunction('!') // Call 3 (ignored)
// After 1000ms of inactivity, this will call someFunction("!")
Throttling limits the rate at which a function can fire. This is useful for events that should not fire too often, such as resizing windows or scrolling pages.
function throttle(func, limit) {
let inThrottle = false
return function (...args) {
const context = this
if (!inThrottle) {
func.apply(context, args)
inThrottle = true
setTimeout(() => (inThrottle = false), limit)
}
}
}
Usage Example
// Define the function that updates the layout
function updateLayout() {
console.log('Resize event handler called')
}
// Create a throttled version of the function
const handleScroll = throttle(updateLayout, 2000)
// Listen for window scroll events and call the throttled function
window.addEventListener('resize', handleScroll)
The once
function ensures that a given function is executed only once, regardless of how many times it is called.
function once(func) {
let called = false;
let result;
return function(...args) {
if (!called) {
called = true;
result = func.apply(this, args);
}
return result;
};
}
Usage Example
const firstTask = once(function() {
console.log('First task executed!');
return 'First task result';
});
const secondTask = once(function() {
console.log('Second task executed!');
return 'Second task result';
});
console.log(firstTask()); // Logs 'First task executed!' and returns 'First task result'
console.log(firstTask()); // Just returns 'First task result'
console.log(secondTask()); // Logs 'Second task executed!' and returns 'Second task result'
console.log(secondTask()); // Just returns 'Second task result'
Memoization is a technique used to speed up programs by storing the results of expensive function calls and reusing them when the same inputs occur again.
function memoize(func) {
const cache = new Map();
return function(...args) {
const key = JSON.stringify(args);
if (cache.has(key)) {
return cache.get(key);
}
const result = func.apply(this, args);
cache.set(key, result);
return result;
};
}
Usage Example
const expensiveCalculation = memoize(function(x, y) {
console.log('Calculating...');
return x + y;
});
// Testing the memoize function
console.log(expensiveCalculation(2, 3)); // Logs 'Calculating...' and returns 5
console.log(expensiveCalculation(2, 3)); // Returns 5 from cache, without logging 'Calculating...'
console.log(expensiveCalculation(4, 5)); // Logs 'Calculating...' and returns 9
console.log(expensiveCalculation(4, 5)); // Returns 9 from cache, without logging 'Calculating...'
Currying is a technique in functional programming where a function with multiple arguments is transformed into a sequence of functions, each with a single argument.
function curry(func) {
return function curried(...args) {
if (args.length >= func.length) {
return func.apply(this, args);
} else {
return function(...moreArgs) {
return curried.apply(this, args.concat(moreArgs));
};
}
};
}
Usage Example
function add(a, b, c) {
return a + b + c;
}
const curriedAdd = curry(add);
console.log(curriedAdd(1)(2)(3)); // 6
console.log(curriedAdd(1, 2)(3)); // 6
console.log(curriedAdd(1)(2, 3)); // 6
console.log(curriedAdd(1, 2, 3)); // 6
Partials are functions that take a function and some arguments, and return a new function that, when called, passes the remaining arguments to the original function.
function partial(func, ...fixedArgs) {
return function(...remainingArgs) {
return func.apply(this, [...fixedArgs, ...remainingArgs]);
};
}
Usage Example
function greet(greeting, name) {
return `${greeting}, ${name}!`;
}
const sayHelloTo = partial(greet, 'Hello');
console.log(sayHelloTo('Alice')); // 'Hello, Alice!'
console.log(sayHelloTo('Bob')); // 'Hello, Bob!'
function add(a, b, c) {
return a + b + c;
}
const add5And3 = partial(add, 5, 3);
console.log(add5And3(2)); // 10
console.log(add5And3(4)); // 12
Piping is a concept borrowed from Unix/Linux pipelines, where the output of one command is piped as input to another. In JavaScript, piping allows you to chain functions together, passing the result of one function as input to the next.
function pipe(...functions) {
return function(initialValue) {
return functions.reduce((acc, fn) => fn(acc), initialValue);
};
}
Usage Example
const double = x => x * 2;
const increment = x => x + 1;
const square = x => x * x;
const processNumber = pipe(double, increment, square);
console.log(processNumber(3)); // 49
Composition is the process of combining simple functions to create more complex ones. In JavaScript, composing functions allows for cleaner, more readable code.
function compose(...functions) {
return function(initialValue) {
return functions.reduceRight((acc, fn) => fn(acc), initialValue);
};
}
Usage Example
const double = x => x * 2;
const increment = x => x + 1;
const square = x => x * x;
const processNumber = compose(square, increment, double);
console.log(processNumber(3)); // 49
The pick
function creates a new object with only the specified properties from the original object.
function pick(obj, keys) {
return keys.reduce((acc, key) => {
if (obj && Object.prototype.hasOwnProperty.call(obj, key)) {
acc[key] = obj[key];
}
return acc;
}, {});
}
Usage Example
const user = {
id: 1,
name: 'John Doe',
email: 'john@example.com',
age: 30
};
const pickedUser = pick(user, 'name', 'email');
console.log(pickedUser);
// Output: { name: 'John Doe', email: 'john@example.com' }
The omit
function creates a new object with all properties except those specified.
function omit(obj, ...keys) {
return Object.keys(obj).reduce((acc, key) => {
if (!keys.includes(key)) {
acc[key] = obj[key];
}
return acc;
}, {});
}
Zipping combines corresponding elements from multiple arrays into tuples.
function zip(...arrays) {
const length = Math.min(...arrays.map(arr => arr.length));
return Array.from({ length }, (_, index) => arrays.map(arr => arr[index]));
}
Usage Example
const names = ['Alice', 'Bob', 'Charlie'];
const ages = [30, 25, 35];
const cities = ['New York', 'San Francisco', 'Seattle'];
const zipped = zip(names, ages, cities);
console.log(zipped);
// Output: [ ['Alice', 30, 'New York'], ['Bob', 25, 'San Francisco'], ['Charlie', 35, 'Seattle'] ]
- MDN Web Docs - JavaScript Guide
- Functional Programming in JavaScript
- Mastering JavaScript Function Patterns
This repository aims to serve as a practical guide and resource for developers looking to leverage advanced JavaScript functions to write better, more efficient code.