Home > Web Front-end > JS Tutorial > Function Composition in JavaScript with Array.prototype.reduceRight

Function Composition in JavaScript with Array.prototype.reduceRight

Lisa Kudrow
Release: 2025-02-14 08:50:12
Original
617 people have browsed it

Function Composition in JavaScript with Array.prototype.reduceRight

In recent years, functional programming in JavaScript has become increasingly popular. While some of its often-published principles (such as invariance) require runtime workarounds, the language's first-class processing of functions proves its support for composable code driven by this basic primitive. Before introducing how to dynamically combine functions from other functions, let's briefly review it.

Key Points

  • Function combinations in JavaScript can create complex functions by combining simpler functions, thereby improving the reusability and modularity of the code. This approach simplifies code understanding, debugging, and testing, and encourages the principle of "don't repeat yourself" (DRY) and reduces redundancy.
  • Advanced-order functions (can take one or more functions as parameters, or return one function as a result) are key parts of function combinations in JavaScript. They are used to create new functions by combining existing functions.
  • JavaScript handles functions as values, allowing easy combination of larger, context-specific jobs. Using higher-order functions results in separation of their definitions and calls. This can free developers from the strict hierarchical constraints imposed by object-oriented programming.
  • Function combinations can be used with JavaScript frameworks such as React or Vue. In React, combining components to build complex user interfaces is a common pattern. Similarly, Vue's mixin system can be regarded as a form of function combinations.

What is a function?

In fact, a function is a procedure which allows execution of a set of imperative steps to perform side effects or return values. For example:

function getFullName(person) {
  return `${person.firstName} ${person.surname}`;
}
Copy after login
Copy after login
Copy after login
Copy after login

When this function is called with an object with the firstName and lastName properties, getFullName returns a string containing the two corresponding values:

const character = {
  firstName: 'Homer',
  surname: 'Simpson',
};

const fullName = getFullName(character);

console.log(fullName); // => 'Homer Simpson'
Copy after login
Copy after login
Copy after login
Copy after login

It is worth noting that since ES2015, JavaScript now supports arrow function syntax:

const getFullName = (person) => {
  return `${person.firstName} ${person.surname}`;
};
Copy after login
Copy after login
Copy after login

Given that our getFullName function has a 1 (i.e. a single parameter) and only has one return statement, we can simplify this expression:

const getFullName = person => `${person.firstName} ${person.surname}`;
Copy after login
Copy after login
Copy after login

Although the methods of these three expressions are different, they all achieve the same purpose in the end:

  • Create a function called getFullName that can be accessed through the name property.
  • Accepts a parameter person.
  • Returns a calculated string containing person.firstName and person.lastName, separated by spaces.

Combining function through return value

In addition to assigning function return values ​​to declarations (e.g. const person = getPerson();), we can also use them to fill the parameters of other functions, or generally provide values ​​where JavaScript allows them. Suppose we have functions that perform logging and sessionStorage side effects:

const log = arg => {
  console.log(arg);
  return arg;
};

const store = arg => {
  sessionStorage.setItem('state', JSON.stringify(arg));
  return arg;
};

const getPerson = id => id === 'homer'
  ? ({ firstName: 'Homer', surname: 'Simpson' })
  : {};
Copy after login
Copy after login
Copy after login

We can do these operations on the return value of getPerson using nested calls:

function getFullName(person) {
  return `${person.firstName} ${person.surname}`;
}
Copy after login
Copy after login
Copy after login
Copy after login

Given that the required parameters need to be provided to the function according to the way it is called, the innermost function will be called first. So, in the above example, the return value of getPerson will be passed to the log, and the return value of log will be forwarded to the store. By combining function calls to build statements, we can eventually build complex algorithms from atomic building blocks, but nesting these calls can become difficult to handle; what would it look like if we wanted to combine 10 functions?

const character = {
  firstName: 'Homer',
  surname: 'Simpson',
};

const fullName = getFullName(character);

console.log(fullName); // => 'Homer Simpson'
Copy after login
Copy after login
Copy after login
Copy after login

Luckily, we can use an elegant general implementation: simplify the array of functions into higher order functions.

Cumulative array using Array.prototype.reduce

The reduce method of the

Array prototype takes an array instance and accumulates it as a single value. If we want to sum a number array, we can use the following method:

const getFullName = (person) => {
  return `${person.firstName} ${person.surname}`;
};
Copy after login
Copy after login
Copy after login
In this code segment, numbers.reduce accepts two parameters: the callback function that will be called on each iteration, and the initial value of the total parameter passed to the callback function; the value returned by the callback function will be in the next iteration pass to total. To further decompose this by studying the above calls to sum:

    Our callback function will run 5 times
  • Since we provide an initial value, total will be 0 on the first call
  • The first call will return 0 2, causing total to resolve to 2 on the second call
  • The result returned by this subsequent call 2 3 will be provided to the total parameter in the third call, etc.
Although the callback function accepts two other parameters, which represent the current index and the array instance that calls Array.prototype.reduce, the first two are the most critical and are usually called:

    Accumulator – The value returned by the last iteration callback function. In the first iteration, this resolves to the initial value, and if no initial value is specified, it resolves to the first item in the array.
  • currentValue – The array value of the current iteration; since it is linear, it will progress from array[0] to array[array.length - 1]
  • during the call to Array.prototype.reduce

Combining function using Array.prototype.reduce

Now that we understand how to simplify arrays into single values, we can use this method to combine existing functions into new functions:

const getFullName = person => `${person.firstName} ${person.surname}`;
Copy after login
Copy after login
Copy after login
Note that we use the rest parameter syntax (...) to cast any number of parameters into an array, so that consumers do not need to explicitly create new array instances for each call site. compose also returns another function, which makes compose a higher order function that accepts an initial value (initialArg). This is crucial because we can therefore combine new, reusable functions without calling them before necessary; this is called

lazy evaluation.

So how do we combine other functions into a single higher order function?

const log = arg => {
  console.log(arg);
  return arg;
};

const store = arg => {
  sessionStorage.setItem('state', JSON.stringify(arg));
  return arg;
};

const getPerson = id => id === 'homer'
  ? ({ firstName: 'Homer', surname: 'Simpson' })
  : {};
Copy after login
Copy after login
Copy after login
In this code:

  • person declaration will resolve to { firstName: 'Homer', incident: 'Simpson' }
  • The above representation of person will be output to the browser's console
  • person will be serialized to JSON and then write sessionStorage
  • under the person key

The importance of call order

Ability to combine any number of functions using composing utilities to make our code more concise and abstract. However, we can highlight one point by revisiting the inline call:

function getFullName(person) {
  return `${person.firstName} ${person.surname}`;
}
Copy after login
Copy after login
Copy after login
Copy after login

People may find it natural to copy this with our compose function:

const character = {
  firstName: 'Homer',
  surname: 'Simpson',
};

const fullName = getFullName(character);

console.log(fullName); // => 'Homer Simpson'
Copy after login
Copy after login
Copy after login
Copy after login

In this case, why does fNested(4) === fComposed(4) resolve to false? You may remember that I emphasized how to interpret the internal call in the first place, so compose(g, h, i) is actually equivalent to x => i(h(g(x))), so fNested returns 10 and fComposed returns 9. We can simply reverse the call order of nested or combined variants of f, but given the specificity of compose to mirror nested calls, we need a way to reduce the function in right-to-left order; JavaScript is lucky to use Array.prototype.reduceRight provides this:

const getFullName = (person) => {
  return `${person.firstName} ${person.surname}`;
};
Copy after login
Copy after login
Copy after login

With this implementation, both fNested(4) and fComposed(4) resolve to 10. However, our getPersonWithSideEffects function is now incorrectly defined; although we can reverse the order of the internal functions, in some cases, reading from left to right can facilitate the psychological analysis of program steps. It turns out that our previous methods have been quite common, but are often called pipe:

const getFullName = person => `${person.firstName} ${person.surname}`;
Copy after login
Copy after login
Copy after login

By using our pipe function, we will keep the left-to-right order required by getPersonWithSideEffects. Pipelines have become a major component of RxJS for reasons outlined above; in this order, it may be more intuitive to think about data streams in combination streams operated by operators.

Function combinations as alternatives to inheritance

We have seen in the previous example how to combine infinite multiple functions into larger, reusable, target-oriented units. Another benefit of function combination is to free yourself from the rigidity of inheritance graphs. Suppose we want to reuse logging and storage behavior based on the class hierarchy; it can be expressed like this:

const log = arg => {
  console.log(arg);
  return arg;
};

const store = arg => {
  sessionStorage.setItem('state', JSON.stringify(arg));
  return arg;
};

const getPerson = id => id === 'homer'
  ? ({ firstName: 'Homer', surname: 'Simpson' })
  : {};
Copy after login
Copy after login
Copy after login

Apart from the length, the direct problem with this code is that we abuse inheritance to implement reuse; if another class extends Loggable, it is also inherently a subclass of the Storage, even if we don't need this logic. A potentially more serious problem is naming conflicts:

function getFullName(person) {
  return `${person.firstName} ${person.surname}`;
}
Copy after login
Copy after login
Copy after login
Copy after login

If we want to instantiate MyState and call its store method, we will not call the Storeable store method unless we add a call to super.store() in MyState.prototype.store, but this will be in State Creates a tight, fragile coupling between Storable. This can be mitigated using entity systems or policy patterns, as I have introduced elsewhere, but despite the advantages of inheriting the broader taxonomy of expression systems, function combinations provide a flat, concise way to share without dependency on Code for method name.

Summary

JavaScript handles functions as values ​​and generates their expressions, making it easy to combine larger, context-specific jobs. Thinking of this task as a cumulative of function arrays eliminates the need for imperative nested calls, and using higher-order functions results in separation of their definitions and calls. Furthermore, we can get rid of the strict hierarchical constraints imposed by object-oriented programming.

FAQs about function combinations in JavaScript

What is the meaning of function combination in JavaScript?

Function combination is a basic concept in JavaScript and large functional programming. It allows developers to create complex functions by combining simpler functions, thereby improving the reusability and modularity of their code. This approach makes the code easier to understand, debug and test. It also encourages the principle of "don't repeat yourself" (DRY) to reduce redundancy in the code base.

How does function combinations relate to the concept of higher-order functions?

High-order functions are a key part of function combinations in JavaScript. A higher-order function is a function that can take one or more functions as parameters, return a function as a result, or perform both operations at the same time. In function combinations, we often use higher-order functions to create new functions by combining existing functions.

Can you provide a practical example of function combinations in JavaScript?

Of course, let's consider a simple example. Suppose we have two functions, double and increment. double takes a number and multiplies it by 2, and increment adds 1 to its input. We can combine these two functions to create a new function that doubles a number and increments the result.

const character = {
  firstName: 'Homer',
  surname: 'Simpson',
};

const fullName = getFullName(character);

console.log(fullName); // => 'Homer Simpson'
Copy after login
Copy after login
Copy after login
Copy after login

What is the difference between function combination and function chain in JavaScript?

Function combination and function chain are two different ways to combine functions in JavaScript. Function combinations involve passing the output of one function as input to another. On the other hand, a function chain involves calling multiple functions in order, where each function calls the result of the previous function. Although both techniques can achieve similar results, function combinations are more in line with the principles of functional programming.

How does function combination help with code testing and debugging?

Function combinations facilitate the creation of small pure functions that do only one thing and do well. These functions are easier to test and debug than large monolithic functions. Since each function is independent, you can test it individually without worrying about the rest of the code. This makes it easier to find and fix bugs in the code.

Is function combinations possible with asynchronous functions in JavaScript?

Yes, function combinations can be used with asynchronous functions in JavaScript. However, this requires more attention, as you need to make sure that the output of an asynchronous function is correctly passed to the next function as input. This usually involves using promise or async/await syntax.

What are the potential drawbacks or challenges of using function combinations in JavaScript?

Although there are many benefits to function combinations, it may also introduce complexity if used improperly. For example, deeply nested function calls can be difficult to read and understand. Furthermore, if the combined functions are not pure functions (i.e. they have side effects), it may lead to unexpected results. Therefore, it is important to use function combinations with caution and combine them with good coding practices.

How does function combinations relate to the concept of currying in JavaScript?

Curriization is a technique in JavaScript where a function with multiple parameters is converted into a series of functions, each function has only one parameter. Currying can be used with functions combinations to create more flexible and reusable functions. In fact, some utility libraries such as lodash and Ramda provide both currying and combinatorial functions.

Is function combinations useful with JavaScript frameworks such as React or Vue?

Yes, function combinations can be used with JavaScript frameworks such as React or Vue. In fact, in React, combining components to build complex user interfaces is a common pattern. Similarly, Vue's mixin system can be regarded as a form of function combinations.

What libraries or tools are there to assist in function combinations in JavaScript?

Yes, there are several libraries that provide utilities for function combinations in JavaScript. Some popular libraries include lodash, Ramda, and Redux (for state management). These libraries provide functions such as compose or pipe, making combining functions easier and more efficient.

The above is the detailed content of Function Composition in JavaScript with Array.prototype.reduceRight. For more information, please follow other related articles on the PHP Chinese website!

Statement of this Website
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn
Latest Articles by Author
Popular Tutorials
More>
Latest Downloads
More>
Web Effects
Website Source Code
Website Materials
Front End Template