Brief introduction & Features

The compose function can take multiple independent functions as parameters, and then concatenate them to return a “composing function.”

“Combination function executes, its internal all function will be in accordance with the order of the combined and perform in the form of a queue and orderly, and before a function return value will be accepted as a function of parameters, so the first execution of” function “of a function can receive multiple parameters, and function after receiving only one parameter (the return value of a function). One or more specified arguments like this are passed in from the entry function of the “composite function” (the first function to execute), and then processed, transmitted, and output in the pipeline of functions cascading multiple combinations.

The execution format of the composite function and the format of the parameters in the pipeline are as follows:

f(h(j(1.2)));
Copy the code

The compose function features:

  • The arguments are multiple functions, and the return value is a “combination function.”
  • All functions within a combinatorial function are executed one by one from right to left (mainly in accordance with the mathematical concept of right-to-left operation).
  • All functions in a composite function take the return value of the previous function except the first function that executes it.

Use form:

let sayHello = (. str) = > `Hello , ${str.join(" And ")}`;
let toUpper = str= > str.toUpperCase();
let combin = compose(
  sayHello,
  toUpper
);

combin("jack"."bob"); // HELLO , JACK AND BOB
Copy the code

Is it more concise to compound functions than to call them? Intuitive?

knowledge

The core of the compose function lies in how to realize the combination and series of multiple functions. Through the analysis of various implementation methods, we’d better master the following knowledge before implementing the compose function:

Nested function calls

When multiple functions are called nested, the order of execution is first inside and then outside, layer by layer.

a(b(c(d())));
// d() -> c() -> b() -> a()
Copy the code

The whole process is like peeling an onion from the inside out.

closure

In simple terms, when a function is returned as a return value by another function, and the function has an identifier (variable) that accesses the scope of its parent function, then the return value function is called a closure.

function example() {
  var fnName = arguments.callee.name;
  return function closure() {
    console.log(fnName);
  };
}
Copy the code

recursive

Recursion is a function that calls itself. It is often used in scenarios where multiple layers are nested but the data structure is relatively fixed to quickly process the data.

var ids = [];
var data = {
  id: "001".child: {
    id: "0002".child: {
      id: "0003"}}};function recursion(data) {
  if ("child" in data) {
    ids.push(data.id);
    arguments.callee(data.child);
  }
}

recursion(data);
Copy the code

Realization & Ideas

The following are several functions to be combined as arguments and will be used in subsequent examples:

function a(a) {
  console.log(a);
  return a + "a";
}
function b(b) {
  console.log(b);
  return b + "b";
}
function c(c) {
  console.log(c);
  return c + "c";
}
var funs = [a, b, c];
Copy the code

Simple chestnuts 🌰

The core of the compose function is the concatenation of functions, and this is best illustrated by this example:

var compose = function(x, y) {
  return function() {
    x(y.apply(this.arguments));
  };
};

compose(
  a,
  b
)(1); //'1ba'
Copy the code

This is just a fixed concatenation of the two parameters, but it’s enough to give us an overview of the compose function’s implementation. We can implement the multi-parameter compose function based on this idea.

Closure + loop

We’ll show you two ways to implement the compose function by combining closures with loops. The first is to concatenate nested combinations of multiple functions in a loop by executing function expressions (IIFE) immediately as closures.

function compose(funs) {
  var combin = null;
  for (var i = 0; i < funs.length; i++) {
    combin = (function(i, combin) {
      return combin
        ? function(args) {
            return combin(funs[i](args));
          }
        : function(args) {
            return funs[i](args);
          };
    })(i, combin);
  }
  return combin;
}
Copy the code

Alternatively, the exact opposite is true. The returned closure loops through the passed functions in order, taking the return value of the previous function as an argument to the next function.

function compose(funs) {
  var len = funs.length;
  var index = len - 1;
  return function() {
    var result = len ? funs[index].apply(this.arguments) : arguments[0];
    while (--index >= 0) {
      result = funs[index].call(this, result);
    }
    return result;
  };
}
Copy the code

Closures + recursion

The combination of closures and recursion is, in effect, using the properties of recursion instead of loop traversal, and the principle of them all makes sense.

Below is my collection of “closure” combined with the example of a “recursive”, it is defined in the closure a “executive function” immediately, and then through the index value of n in the immediate execution function to control the number of “recursive”, each recursive create a separate function scope, and the current recursive are put on a recursive function call, Therefore, multiple recursions will result in multiple nested functions, but due to the order in which the nested functions are executed, only the last recursive function will be the first entry function to be executed.

function compose(. arr) {
  return function(. arr2) {(function aa(n) {
      if (n < arr.length - 1) {
        return arr[n](aa(++n));
      } else {
        returnarr[n](... arr2); }}) (0);
  };
}
Copy the code

In terms of the number of recursions, the nested form of this combination function is as follows:

step1 : arr[0](aa(++n)); step2 : arr[0](arr[1](aa(++n))) step3 : arr[0](arr[1](arr[2](... arr2)));Copy the code

In fact, this implementation also lacks the final return function of parameters being transmitted and processed in the function pipeline. The following is an improved implementation in ES5 mode:

function compose(funs) {
  return function() {
    var args = arguments; // References the arguments for the closure function
    // Return the result of executing the function immediately
    return (function IIFE(n) {
      if (n < funs.length - 1) {
        return funs[n](IIFE(++n));
      } else {
        return funs[n](args[0]);
      }
    })(0);
  };
}
Copy the code

Below this way way with the above one of the biggest difference is that it does not then performs multiple functions are realized by recursion nested, but by recursion one by one, and will be the last time the result of the execution as a parameter to the next is going to take a function to be executed by the recursive, till the end of the recursion, and back end processing parameters of good results.

function compose(funs) {
  var len = funs.length;
  var index = len - 1; // Execute from right to left.
  var result; // Save the result of execution or recursive execution.
  return function recursion() {
    result = funs[index].apply(this.arguments);
    if (index <= 0) {
      index = len - 1; // If the condition is not met, reset the recursive index
      return result; // Return the final result
    }
    index--;
    return recursion.call(this, result);
  };
}
Copy the code

This is a more logical approach than the previous one, but there are a few things that need to be considered: why does the closure function recursion need to return twice?

  • The first onereturnUsed to return the final result after the last recursive execution and prevent further recursions.
  • The secondreturnIt actually exists in every recursively executed function, returning arguments at every level until the result is returned outside of the closure function.

The execution process of this example is as follows:

----------------------------- ////////// step1 : / / / / / / / / / / -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- / / end of the normal execution, begin to enter the recursion, return here to return the results out of the closure. // After entering the recursion, the outermost layer will pause and wait for the inner layer's recursion to complete before returning the result. return recursion.call(this, '1c'); ----------------------------- ////////// step2 : / / / / / / / / / / -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- / / recursive implementation, begin to enter the second recursion, here return to return the results to the upper layer of the return recursion. The call (this, '1 cb); -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - / / / / / / / / / / step3: / / / / / / / / / / -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- / / last recursive implementation. If (index <= 0) {// reset the recursive index control; // Terminate the subsequent recursive execution and return the result to the previous recursive function. return result; }Copy the code

A more concise ES6 approach.

function compose(. funcs) {
  if (funcs.length === 0) {
    return arg= > arg;
  }

  if (funcs.length === 1) {
    return funcs[0];
  }

  return funcs.reduce((a, b) = >(... args) => a(b(... args))); }Copy the code

The focus of the ES6 approach is to utilize the Reduce merge function of arrays. Each traversal of the merge returns the last combined function and combines it with the current function parameters. You accumulate the combination in sequence and return the combination function.

----------------------------- ////////// step1 : ////////// ----------------------------- a = f(a) ; b= f(b); Return value: (... args) => a(b(... args)) ----------------------------- ////////// step2 : ////////// ----------------------------- a = (... args) => a(b(... args)); b = f(c); Return value: (... args) => ((... args) => a(b(... args))(c(... args)))Copy the code

From the execution steps above, we can see that the rightmost function will be in the outermost layer of the composition, thus ensuring the order of execution from right to left.

Here are some minor changes to the ES6 approach:

function compose(. funs) {
  if (funs.length === 0) {
    return arg= > arg;
  }
  if (funs.length === 1) {
    return funs[0];
  }

  return funs.reverse().reduce((a, b) = >(... arg) => b(a(arg))); }Copy the code

There are two main changes to this example: The first is to reverse the array of functions (funs) so that the rightmost function is traversed first. The second is to reverse the order of the functions, so that each traversal wraps the combined functions around the current function, so that it appears to be nested a(b(c(… Args))) to implement a combination of functions. Only in this way can the function traversed first be the innermost function of the combination, and thus be executed first in the execution order of the nested functions (from right to left).

----------------------------- ////////// step1 : ////////// ----------------------------- a = f(c) ; b= f(b); Return value: (... args) => b(c(... args)) ----------------------------- ////////// step2 : ////////// ----------------------------- a = (... args) => b(c(... args)); b = f(a); Return value: (... arg) => (a((... args) => b(c(... args))(... args)))Copy the code

Pipe and compose

The pipe function and the compose function both return a “composition function,” but the difference is that they are executed from left to right and from right to left. Implementing the pipe function is as simple as adjusting the wrapping order of the compose function.

reference

Segmentfault.com/a/119000000…

For more articles, check out my Github-day-Up project