Get this book Free NOW
https://www.amazon.com/dp/B0DR4D8BSW
January 6, 2025 | January 10, 2025 | https://www.amazon.com/dp/B0DR4D8BSW | https://www.amazon.ca/dp/B0DR4D8BSW |
JavaScript Handbook Advanced Functions
Summary
“JavaScript Handbook: Advanced Functions” delves into the essential concepts and practices that elevate JavaScript from a basic scripting language to a tool for building sophisticated, maintainable, and scalable applications.
Covering topics like higher-order functions, currying, composition, and functional programming principles, this book is designed to help developers write cleaner and more reusable code. Through clear explanations, hands-on examples, and exercises, it bridges the gap between theory and practical application. Whether you’re optimizing existing projects or crafting new solutions, this guide empowers you with the knowledge to succeed in modern JavaScript development.
Introduction
Welcome to “JavaScript Handbook: Advanced Functions”, a comprehensive exploration of some of the most powerful and versatile tools in JavaScript. This book is tailored for developers who want to deepen their understanding of advanced JavaScript concepts, including higher-order functions, currying, composition, and more.
From real-world examples to coding exercises, you’ll learn how these advanced techniques can make your code more modular, readable, and maintainable. Whether you’re a seasoned developer or an ambitious learner, this book will expand your JavaScript toolkit, enabling you to write cleaner, more efficient, and reusable code.
Understanding Higher-Order Functions in JavaScript
What Are Higher-Order Functions?
In JavaScript, higher-order functions are functions that either:
- Take one or more functions as arguments, or
- Return a new function as a result (or both).
This concept is at the heart of functional programming and greatly contributes to code reusability, modularity, and expressive power.
Common Examples of Higher-Order Functions
- Array methods like map(), filter(), reduce(): These methods accept functions as arguments.
- Event handlers and callbacks: Passing a function to addEventListener() in the browser or setTimeout() in Node.js is using a higher-order function.
- Function decorators and wrappers: A function that takes another function and returns a modified version of it.
Why Use Higher-Order Functions?
- Abstraction: They allow you to abstract operations on data sets.
- Reusability: Write general-purpose functions that can be customized by passing in different functions.
- Cleaner Code: Reduce boilerplate and make code more declarative.
Examples of Higher-Order Functions
Passing a function as an argument:
function greet(name) {
console.log(“Hello, ” + name);
}
function sayHello(func, person) {
func(person); // func is a function passed as an argument
}
sayHello(greet, “Alice”); // “Hello, Alice”
Returning a function:
function multiplier(factor) {
return function(x) {
return x * factor;
};
}
const double = multiplier(2);
console.log(double(5)); // 10
Array methods:
let numbers = [1, 2, 3, 4];
let doubled = numbers.map((num) => num * 2); // map takes a function as an argument
console.log(doubled); // [2, 4, 6, 8]
let evenNumbers = numbers.filter((num) => num % 2 === 0);
console.log(evenNumbers); // [2, 4]
Callbacks:
setTimeout(() => {
console.log(“This runs after 1 second”);
}, 1000);
Function that returns a decorator:
function once(fn) {
let done = false;
return function(…args) {
if (!done) {
done = true;
return fn(…args);
}
};
}
let logOnce = once((msg) => console.log(msg));
logOnce(“First call”); // “First call”
logOnce(“Second call”); // no output because fn only runs once
Tips for Using Higher-Order Functions
- Keep your callback functions pure and simple.
- Use arrow functions for concise inline definitions.
- Think in terms of data transformations rather than loops—map, filter, reduce are powerful abstractions.
Multiple-Choice Questions
- What is a Higher-Order Function in JavaScript?
A. A function that only takes numbers as arguments.
B. A function that returns a promise.
C. A function that takes or returns another function.
D. A function that only runs once.
Answer: C
Explanation: Higher-order functions are defined by their ability to take functions as arguments or return them. - Which of the following array methods is a higher-order function?
A. push()
B. pop()
C. map()
D. length property
Answer: C
Explanation: map() takes a function as an argument, making it higher-order. - In array.map(fn), what is fn?
A. A callback function provided to map.
B. A string.
C. The array length.
D. A boolean value.
Answer: A
Explanation: map() expects a callback function that it applies to each element.
What does the following function return?
function add(a) {
return function(b) {
return a + b;
};
}
- A. A number
B. An object
C. A function
D. Undefined
Answer: C
Explanation: add(a) returns a function that takes b and returns a+b. - Which statement best describes filter()?
A. It changes the original array.
B. It returns a new array with elements that satisfy the given condition.
C. It always returns a single value.
D. It only works on strings.
Answer: B
Explanation: filter() returns a new array of elements for which the callback returns true. - reduce() in JavaScript: A. Takes a callback and reduces the array to a single value.
B. Always returns an array.
C. Does not allow initial values.
D. Returns the largest element of the array.
Answer: A
Explanation: reduce() applies a function against an accumulator to reduce the array to a single value. - Which of the following is NOT a higher-order function usage?
A. Passing a function as an argument.
B. Returning a function from another function.
C. Storing a function in a variable.
D. Writing a function that takes no arguments.
Answer: D
Explanation: A function that takes no arguments isn’t necessarily higher-order. Higher-order relates to functions as arguments or return values. - What does setTimeout() exemplify in JavaScript?
A. A pure function
B. A higher-order function that takes a callback
C. A synchronous iteration method
D. A function that returns another function
Answer: B
Explanation: setTimeout() takes a function (callback) as an argument, making it higher-order. - What is a callback function?
A. A function that is passed as an argument to another function.
B. A function that runs immediately upon definition.
C. A function that never returns a value.
D. A function that only works with promises.
Answer: A
Explanation: A callback is a function passed into another function to be invoked later. - once(fn) is a higher-order function that: A. Runs the given function multiple times.
B. Returns a new function that runs fn only once.
C. Immediately calls fn.
D. Does not accept functions as arguments.
Answer: B
Explanation: once(fn) returns a function that ensures fn runs at most once. - If functionA returns functionB, what can we say about functionA?
A. It is a callback.
B. It is a higher-order function.
C. It is a promise.
D. It is a pure function only.
Answer: B
Explanation: Returning a function makes functionA a higher-order function. - Which built-in JavaScript functions are considered higher-order?
A. alert()
B. parseInt()
C. Array.prototype.map()
D. Math.max()
Answer: C
Explanation: map() accepts a function as an argument, thus is higher-order. - What does the reduce() callback function receive as arguments?
A. The current element only
B. The accumulator, current element, current index, and the entire array
C. Only the initial accumulator value
D. No arguments
Answer: B
Explanation: The reduce() callback gets (accumulator, currentValue, currentIndex, array). - Higher-order functions are closely related to which programming paradigm?
A. Object-Oriented Programming
B. Functional Programming
C. Procedural Programming
D. Assembly-level programming
Answer: B
Explanation: Higher-order functions are a key concept in functional programming. - What is the return value of [“a”,”b”,”c”].map((x) => x.toUpperCase())?
A. A transformed array [“A”,”B”,”C”]
B. The original array [“a”,”b”,”c”]
C. A single string
D. Undefined
Answer: A
Explanation: map() returns a new array with the transformation applied. - Can a higher-order function be anonymous?
A. No, it must be named.
B. Yes, it can use arrow functions or unnamed function expressions.
C. Only if defined at the global scope.
D. It must always be assigned to a variable.
Answer: B
Explanation: Higher-order functions can be anonymous, e.g., using arrow functions. - Which method stops execution after finding a matching element? A. map()
B. forEach()
C. find()
D. filter()
Answer: C
Explanation: find() returns the first found element and then stops.
What does the following code log?
[1,2,3].filter(x => x > 1).map(x => x * 2)
- A. [2, 4, 6]
B. [4, 6]
C. [1, 2, 3]
D. [4]
Answer: A
Explanation: filter(x > 1) gives [2,3], map(x*2) on that gives [4,6]. Wait, carefully re-check. The original array is [1,2,3]. Filter x > 1 returns [2, 3]. Then map x * 2 on [2, 3] gives [4, 6]. The correct answer is [4, 6], not [2,4,6].
Correction: The correct answer is [4, 6]. - If you have a function decorate(fn) that returns a new function wrapping fn, what is decorate?
A. A normal function
B. A higher-order function (it returns a function)
C. A promise
D. A class
Answer: B
Explanation: Returning a new function makes decorate a higher-order function. - forEach() vs map(): A. Both return new arrays.
B. forEach() returns undefined, map() returns a new array.
C. forEach() and map() return the same result.
D. forEach() returns the original array, map() returns a new array.
Answer: B
Explanation: forEach() returns undefined, while map() returns a new array.
10 Coding Exercises with Full Solutions and Explanations
Exercise 1: Create a Higher-Order Function that Returns a Function
Task: Write a function multiplyBy(factor) that returns a new function that multiplies its argument by factor.
Solution:
function multiplyBy(factor) {
return function(num) {
return num * factor;
};
}
const triple = multiplyBy(3);
console.log(triple(10)); // 30
Explanation: multiplyBy is a HOF because it returns a function.
Exercise 2: Using Array.map with a Callback
Task: Given an array of numbers [1,2,3], use map() to create an array of their squares.
Solution:
let arr = [1,2,3];
let squares = arr.map(x => x*x);
console.log(squares); // [1,4,9]
Explanation: map() takes a callback function, making it a higher-order function.
Exercise 3: Implement a filterEven Function Using filter()
Task: Write a function filterEven(numbers) that uses filter() to return only even numbers.
Solution:
function filterEven(numbers) {
return numbers.filter(num => num % 2 === 0);
}
console.log(filterEven([1,2,3,4,5,6])); // [2,4,6]
Explanation: filter() is a HOF that takes a function to determine which elements to keep.
Exercise 4: Create a once Higher-Order Function
Task: once(fn) should return a new function that calls fn only the first time and ignores subsequent calls.
Solution:
function once(fn) {
let called = false;
return function(…args) {
if (!called) {
called = true;
return fn(…args);
}
};
}
let onlyOnce = once((msg) => console.log(msg));
onlyOnce(“Hello”); // “Hello”
onlyOnce(“World”); // no output
Explanation: once returns a new function, making it a HOF.
Exercise 5: Use reduce() to Sum an Array
Task: Use reduce() to sum the numbers in [1,2,3,4].
Solution:
let sum = [1,2,3,4].reduce((acc, val) => acc + val, 0);
console.log(sum); // 10
Explanation: reduce() is a HOF, taking a callback that processes each element.
Exercise 6: Create a Function that Takes a Callback
Task: Create a function applyTwice(fn, value) that applies fn to value twice.
Solution:
function applyTwice(fn, value) {
return fn(fn(value));
}
function add2(x) {
return x + 2;
}
console.log(applyTwice(add2, 5)); // 9 (5+2=7, then add2(7)=9)
Explanation: applyTwice is a HOF since it takes fn as an argument.
Exercise 7: Partial Application Using a Returned Function
Task: Write a function add(a) that returns another function taking b and returning a+b.
Solution:
function add(a) {
return function(b) {
return a + b;
};
}
const add5 = add(5);
console.log(add5(10)); // 15
Explanation: add returns a function, making it higher-order.
Exercise 8: Implement a find Polyfill
Task: Write a myFind function that mimics find() using forEach().
Solution:
function myFind(arr, predicate) {
let found;
arr.forEach(element => {
if (found === undefined && predicate(element)) {
found = element;
}
});
return found;
}
console.log(myFind([1,2,3,4], x => x > 2)); // 3
Explanation: myFind takes a predicate function. forEach() is also a HOF.
Exercise 9: Compose Functions
Task: Write a compose(f,g) function that returns a new function (x) => f(g(x)).
Solution:
function compose(f, g) {
return function(x) {
return f(g(x));
};
}
function double(x) { return x * 2; }
function increment(x) { return x + 1; }
let doubleThenIncrement = compose(increment, double);
console.log(doubleThenIncrement(5)); // increment(double(5))= increment(10)=11
Explanation: compose returns a new function, making it a HOF.
Exercise 10: Chain Array Methods
Task: Given [1,2,3,4,5], use filter() and map() to first filter out odd numbers and then double the even numbers.
Solution:
let data = [1,2,3,4,5];
let transformed = data
.filter(num => num % 2 === 0)
.map(num => num * 2);
console.log(transformed); // [4,8]
Explanation: Both filter() and map() are higher-order functions.
Summary
Higher-order functions are central to JavaScript’s functional programming capabilities. By passing functions as arguments or returning them, you can create powerful abstractions, cleaner code, and reusable logic. Functions like map, filter, reduce, along with custom HOFs, enable flexible and expressive programming patterns.
Understanding Function Currying in JavaScript
What Is Currying?
Currying is a functional programming technique where a function that takes multiple arguments is transformed into a series of functions, each taking a single argument and returning a new function until all arguments have been provided.
For example, a function f(a, b, c) that takes three arguments can be turned into f(a)(b)(c), where each invocation returns a new function waiting for the next argument. The final call, after receiving all arguments, returns the result.
Why Curry Functions?
- Reusability & Modularity:
Currying allows you to reuse functions with some arguments pre-applied and wait to receive the remaining arguments later. - Partial Application:
Currying enables partial application — supplying some arguments now and the rest later, resulting in more flexible and composable code. - Functional Programming Style:
Currying promotes a declarative and functional style, making logic more explicit and testable.
Currying vs Partial Application
- Currying transforms a function of N arguments into N single-argument functions.
- Partial Application is about applying some arguments now and returning a function that expects the remaining arguments. While closely related, partial application doesn’t strictly require that the resulting function always takes one argument at a time; it can return a function still accepting multiple arguments.
Simple Currying Example
Without Currying:
function add(a, b) {
return a + b;
}
console.log(add(2, 3)); // 5
With Currying:
function add(a) {
return function(b) {
return a + b;
};
}
console.log(add(2)(3)); // 5
Curried Functions for More Arguments
For a function sum(a, b, c):
function sum(a, b, c) {
return a + b + c;
}
// Curried version:
function sumCurry(a) {
return function(b) {
return function(c) {
return a + b + c;
};
};
}
console.log(sumCurry(1)(2)(3)); // 6
Generic Currying Utility Function
We can write a utility to curry any function automatically:
function curry(fn) {
return function curried(…args) {
if (args.length >= fn.length) {
// If enough arguments have been supplied
return fn(…args);
} else {
// Otherwise, return a function expecting the remaining arguments
return function(…rest) {
return curried(…args, …rest);
};
}
};
}
function multiply(a, b, c) {
return a * b * c;
}
const curriedMultiply = curry(multiply);
console.log(curriedMultiply(2)(3)(4)); // 24
console.log(curriedMultiply(2,3)(4)); // 24 (partial application)
When to Use Currying?
- When you want to avoid passing the same argument multiple times.
- Creating specialized versions of a function for reuse.
- Making code more composable and functional.
Multiple-Choice Questions
- What is currying?
A. Cooking a function slowly.
B. Transforming a multi-argument function into a series of unary functions.
C. A method to call a function multiple times in a loop.
D. Converting an object into a function.
Answer: B
Explanation: Currying is about splitting a function taking multiple arguments into multiple functions taking one argument each. - Which is a curried form of f(a,b) = a+b?
A. f(a)(b) = a+b
B. f(a,b,c)= a+b+c
C. f()() with no arguments
D. f(a,b)= a-b
Answer: A
Explanation: f(a)(b) is the curried form of a function taking two arguments. - Currying vs Partial Application:
A. They are identical concepts.
B. Currying always returns unary functions, partial application doesn’t have to.
C. Partial application requires all arguments at once.
D. Currying doesn’t allow partial application.
Answer: B
Explanation: Curried functions return one-argument functions at each step. Partial application is more general. - If add is a curried function of 2 arguments, how do you call it?
A. add(a,b)
B. add()(a)(b)
C. add(a)(b)
D. add([a,b])
Answer: C
Explanation: A 2-argument curried function is called as add(a)(b). - What does a curried function return when not all arguments are provided?
A. The final result
B. A function waiting for the remaining arguments
C. undefined
D. An error
Answer: B
Explanation: A curried function returns a new function if it doesn’t receive all arguments. - Currying makes it easier to:
A. Change the arity (number of arguments) of a function at runtime.
B. Partially apply arguments for future use.
C. Directly call async functions.
D. Avoid closures.
Answer: B
Explanation: Currying naturally supports partial application of arguments. - Given a curried function f(a)(b)(c), how many arguments does the original function take?
A. 1
B. 2
C. 3
D. Unknown
Answer: C
Explanation: There are three successive calls, each taking one argument, so originally it took three arguments. - If curry(fn) transforms fn, what must fn have?
A. A known fixed arity (length)
B. Arbitrary arguments only
C. Always a single argument
D. No return value
Answer: A
Explanation: The curry utility typically uses fn.length to know how many arguments fn expects. - Which of the following is a benefit of currying?
A. Code becomes imperatively structured.
B. Functions become harder to reuse.
C. Simplifies function composition and reuse.
D. No difference in code clarity.
Answer: C
Explanation: Currying often leads to more composable and reusable code. - In which programming paradigm is currying a common practice?
A. Object-Oriented Programming
B. Functional Programming
C. Procedural Programming
D. Imperative Programming
Answer: B
Explanation: Currying is a common practice in functional programming. - If a curried function takes 4 arguments, how many nested functions will it typically produce?
A. 1
B. 2
C. 3
D. 4
Answer: D
Explanation: It produces a chain of 4 one-argument functions. - What happens if you call a curried function with all its required arguments at once if the curry function supports partial application?
A. It returns the final result immediately.
B. It returns another function.
C. It throws an error.
D. It returns undefined.
Answer: A
Explanation: If all required arguments are passed, the curried function returns the final result. - A curried multiply(2)(3) equals:
A. 2
B. 3
C. 6
D. A function expecting arguments
Answer: C
Explanation: multiply curried: multiply(2)(3) = 2 * 3 = 6. - Currying a function that takes n arguments results in:
A. A function that can only take one argument at a time until all are provided.
B. A single function that still requires n arguments at once.
C. A function that no longer requires arguments.
D. No change to the function’s calling style.
Answer: A
Explanation: Currying transforms it into a sequence of unary functions. - The curry() utility function often checks:
A. fn.name property
B. fn.length property
C. fn.toString() result
D. fn.constructor
Answer: B
Explanation: fn.length gives the number of parameters fn expects, useful for deciding when all arguments are supplied. - Which is true about currying and side effects?
A. Currying requires no side effects for correctness.
B. Currying is unrelated to side effects; it’s about argument handling.
C. Currying only works with pure functions.
D. Currying always introduces side effects.
Answer: B
Explanation: Currying doesn’t mandate purity or side effects; it’s about how arguments are supplied. - Can you use arrow functions to create curried functions?
A. Yes, arrow functions can also return functions.
B. No, you must use traditional function syntax.
C. Only with async functions.
D. Only if the function has one argument.
Answer: A
Explanation: Arrow functions can be used freely in curried functions. - If a curried function needs more arguments than provided in the first call, it returns:
A. An error
B. A partially applied function waiting for the rest of the arguments
C. The final result anyway
D. null
Answer: B
Explanation: The key idea of currying is returning a function waiting for the remaining arguments. - What is the best scenario to apply currying?
A. When you need to pass the same argument repeatedly to many functions.
B. When you only call a function once.
C. For CPU-intensive operations.
D. To handle exceptions.
Answer: A
Explanation: Currying helps reuse functions with some arguments fixed, useful if the same argument is needed repeatedly. - Which is a valid curried calling style for a 3-argument function f?
A. f(a,b,c)
B. f(a)(b)(c)
C. f(a)(b,c)
D. f([a,b,c])
Answer: B
Explanation: The standard curried form for a 3-argument function is f(a)(b)(c).
10 Coding Exercises with Full Solutions and Explanations
Exercise 1: Curried Addition
Task: Create a curried function add(a)(b) that returns a+b.
Solution:
function add(a) {
return function(b) {
return a + b;
};
}
console.log(add(2)(3)); // 5
Explanation: Basic two-argument curried function.
Exercise 2: Curried Triple Argument Function
Task: Create a curried function sum(a)(b)(c) that returns a+b+c.
Solution:
function sum(a) {
return function(b) {
return function(c) {
return a + b + c;
};
};
}
console.log(sum(1)(2)(3)); // 6
Explanation: Three nested functions, each taking one argument.
Exercise 3: Generic Curry Function
Task: Write a curry(fn) function that can curry a function with any number of arguments.
Solution:
function curry(fn) {
return function curried(…args) {
if (args.length >= fn.length) {
return fn(…args);
} else {
return (…rest) => curried(…args, …rest);
}
};
}
function multiply(a, b, c) {
return a * b * c;
}
const curriedMultiply = curry(multiply);
console.log(curriedMultiply(2)(3)(4)); // 24
Explanation: Uses the function’s length property and rest parameters to handle partial application.
Exercise 4: Using Curried Functions for Configuration
Task: Create a curried function config(env)(debug)(port) that returns an object {env, debug, port}.
Solution:
function config(env) {
return function(debug) {
return function(port) {
return { env, debug, port };
};
};
}
console.log(config(“production”)(true)(8080));
// {env: “production”, debug: true, port: 8080}
Explanation: Curried arguments to build a configuration object step by step.
Exercise 5: Partially Apply Arguments with Curry
Task: Given the curried sum from Exercise 2, create a partially applied version that always adds 10 first.
Solution:
const add10 = sum(10);
console.log(add10(5)(2)); // sum(10)(5)(2) = 17
Explanation: Using partial application, we fix a=10 and then only need two more calls.
Exercise 6: Curry a Function with curry() Utility
Task: Use the curry() function from Exercise 3 to curry a concat function that concatenates three strings.
Solution:
function concat(a, b, c) {
return a + b + c;
}
const curriedConcat = curry(concat);
console.log(curriedConcat(“Hello”)(” “)(“World”)); // “Hello World”
Explanation: curry() handles argument accumulation.
Exercise 7: Curried Filtering
Task: Create a curried function filterBy(predicate)(array) that filters array by predicate.
Solution:
function filterBy(predicate) {
return function(array) {
return array.filter(predicate);
};
}
const isEven = x => x % 2 === 0;
console.log(filterBy(isEven)([1,2,3,4])); // [2,4]
Explanation: The first call sets the predicate, the second call filters the array.
Exercise 8: Curried Map
Task: Create a curried function mapWith(fn)(array) that maps array using fn.
Solution:
function mapWith(fn) {
return function(array) {
return array.map(fn);
};
}
const double = x => x*2;
console.log(mapWith(double)([1,2,3])); // [2,4,6]
Explanation: Similar pattern to filtering, but with map.
Exercise 9: Reuse Curried Functions for Composability
Task: Use the filterBy and mapWith from previous exercises to first filter even numbers and then double them.
Solution:
console.log(mapWith(double)(filterBy(isEven)([1,2,3,4,5,6]))); // [4,8,12]
Explanation: Curried functions allow piping: mapWith(double)(filterBy(isEven)(…)).
Exercise 10: Curry a Preexisting Function
Task: Curry the built-in Math.min which takes multiple arguments, and try calling it as curriedMin(5)(3)(2).
Note: Math.min can take multiple arguments, but we must consider a fixed number for demonstration. Let’s assume we want a version that only compares three numbers.
Solution:
function curry(fn) {
return function curried(…args) {
if (args.length >= fn.length) {
return fn(…args);
} else {
return (…rest) => curried(…args, …rest);
}
};
}
// Let’s create a 3-argument wrapper for Math.min
function min3(a, b, c) {
return Math.min(a, b, c);
}
const curriedMin = curry(min3);
console.log(curriedMin(5)(3)(2)); // 2
Explanation: By currying a three-argument version of Math.min, we can call it in a curried style.
Summary
Currying is a powerful technique that transforms a function with multiple arguments into a nested series of one-argument functions. This approach enhances code reusability, composability, and clarity in functional programming. Through the exercises and examples given, you now have a solid understanding of how to implement and leverage currying in JavaScript, as well as a set of questions and answers to consolidate your knowledge.