preface

In my work, I often encounter situations where I need to enhance the function of the original function, such as printing the parameter list or return value of the function, and making the function run only once. At first is to modify a function directly, convenient and quick, until one day to modify the accidentally changed the original code, joined a hide the bug is not easy to be recognized, finally caused a certain influence, so consider is there a more elegant way to enhance the function of the original function, finally found the answer – higher-order functions.

demand

Before we do that, let’s look at a piece of code:

function sum(. args) {
  return args.reduce((pre, cur) = > pre + cur, 0);
}

console.log(sum(1.2.3.4.5));  / / output: 15
Copy the code

This is a very simple summation code. Suppose we now receive a new requirement to print out the argument and return value of the argument when calling this function. What do we do?

Inelegant implementation

Without more words, directly on the source code:

function sum(. args) {
  console.log("Parameter =>", args);
  let result = args.reduce((pre, cur) = > pre + cur, 0);
  console.log("Result =>, result);
  return result;
}

console.log(sum(1.2.3.4.5)); 

// Parameter => [1, 2, 3, 4, 5]
// Result => 15
/ / 15
Copy the code

We satisfied this requirement by modifying the source code, but the implementation of this version is not elegant enough for the following reasons:

  • Violate the open and close principle of design pattern and introduce new bugs when adding new features.
  • It can’t be reused. If you now have a function sub that also needs to print parameters and results, you need to rewrite the logic.

Elegant implementation – higher order functions

What is a higher-order function

In short, a function that returns a value of a function or an argument of a function. In js, array. prototype map, forEach, etc. are high-order functions.

High order function version implementation

In the following example, we use higher-order functions to fulfill the requirements.

function sum(. args) {
  return args.reduce((pre, cur) = > pre + cur, 0);
}

function logParamsAndResult(fn) {
  return function (. args) {
    console.log("Parameter =>", args);
    let result = fn.apply(this, args);
    console.log("Result =>, result);
    return result;
  };
}

let sumPro = logParamsAndResult(sum);
console.log(sumPro(1.2.3.4.5));

// Parameter => [1, 2, 3, 4, 5]
// Result => 15
/ / 15
Copy the code

In this version of the implementation, we do not modify the original code, but return a function that prints the parameters passed in, and call the original function through apply to get the return value, and finally implement the requirements. Compared to the previous version, the implementation has the following advantages:

  • Without modifying the original code, the functionality of the function is enhanced without invasion.
  • Reuse is strong, for all the functions that need to print parameters and return values, can be wrapped with this higher-order function.

Since these functions are used to enhance existing functions, they are called enhancers or enhancers

How to write an enhancement function

Before we discuss how to write an enhancer, let’s look at two classic enhancer examples for reference:

function once(fn) {
  let called = false;
  return function (. args) {
    if (called) return;
    called = true;
    let ret = fn.apply(this, args);
    return ret;
  };
}

function withCache(fn) {
  let cache = {};
  return function (. args) {
    let key = JSON.stringify(args);
    return cache[key] || (cache[key] = fn.apply(this, args));
  };
}
Copy the code

The once function returns a function that is executed only once, using the called variable to ensure that the fn function passed in is executed only once. The withCache function realizes the cache through the cache variable, so that the FN function has the cache function. From the implementation of the above enhancement functions, it is not difficult to see that their implementation is a certain pattern, see the following code comment:

function wrapper(fn) {
  // 1 closure processing
  return function (. args) {
    // 2 Interception parameters
    const ret = fn.apply(this, args);
    // 3 Intercepts the return value
    return ret;
  };
}
Copy the code

Forget what supergiant said, there is no problem in computing that can’t be solved by adding an indirect middle layer. If there is, add another layer. Enhancement is nothing more than adding an intermediate layer (the returned function) to intercept function calls and extend function functionality. As shown in the above example, the core of the code is to call the original function via const ret = fn.apply(this, args). There are three areas where we can extend the function’s functionality:

  • inNote 1Here, we can use the closure mechanism to implement some special functions, such as those described aboveoncewithCacheFunctions;
  • In comment 2, we can intercept the parameters and then process them;
  • Here in comment 3, the return value can be intercepted and processed.

These are the three places where we write enhancement functions to extend functions. In addition, there are two points to note:

  • In the core codeconst ret = fn.apply(this, args)So if I call this,applyThe first argument to this must be this instead ofnullorfn(read other people’s blogs before someone wrote this), so as to ensurefnIn the callthisCan dynamically bind to the object calling it. Specific can refer toJavascript You Don’t KnowRoll up aboutthisThe interpretation of the (Strongly recommend)
  • After intercepting the return value of a function, remember to return it.

conclusion

The use of higher-order functions allows us to elegantly enhance function functions, which are worth using in our work. The writing of such enhancement functions is very clear, and as long as we master the essence, we can write more elegant code.