Personal summary:

Higher-order functions are characterized by whether the input is a function or whether the return is a function. Input function is practical to perform the function of the operation or to complete the function, but the function is as a process is retained, the original complete the processing of the common parts of the code of spun off (or abstract), namely the abstract process, concrete business as a function of the code are preserved, which is passed in as the parameters of the higher-order functions function; Then, advanced function after finishing the process of abstraction, the decoration of the passed to the function (i.e., return function), the scope of the parameters, intact to the input function (that is, the actual finish operation specific function) and execute, and executed directly input function is the same effect (and abstracts the process of scene processing code), Therefore, the input function is written almost exactly as the code function intended to be implemented. In this way, we can wrap the original function without affecting it. This is where the function interceptor comes from.once,debounce,throttleThe implementation comes from a pure process abstraction. Pure functions come from keeping all the code that affects the external environment, non-idempotent code, and abstracting the implementation of other processes.

The return function of a higher-order function is just a wrapper, carrying the abstracted process and connecting the parameters and scope of the original function.

This section looks at how process abstraction works. Process abstraction can improve the maintainability of a system while simplifying the code for additional processing logic and reducing logic pitfalls.

It’s so hard, you can barely read it later, you can only work backwards step by step, and it feels almost impossible to design and implement it yourself (creating higher order functions)…

The binding event is executed only once

It is not uncommon for an event handler to be executed only once.

For example, the following item list processing:

<ul>
  <li><button><span>Task 1: Learn HTML</span></button></li>
  <li><button><span>Task 2: Learn CSS</span></button></li>
  <li><button><span>Task 3: Learn JavaScript</span></button></li>
</ul>
Copy the code
ul {
  padding: 0;
  margin: 0;
  list-style: none;
}

li button {
  border: 0;
  background: transparent;
  outline: 0 none;
}

li.completed {
  transition: opacity 2s;
  opacity: 0;
}

li button:before {
    content: '☑ ️';
    cursor: pointer;
    padding-right: 2px;
}

li.completed button:before {
  content: '✅';
  cursor: pointer;
  padding-right: 2px;
}
Copy the code
const list = document.querySelector('ul');
const buttons = list.querySelectorAll('button');
buttons.forEach((button) = > {
    button.addEventListener('click'.(evt) = > {
        const target = evt.currentTarget;
        target.parentNode.className = 'completed';
        setTimeout(() = > {
            list.removeChild(target.parentNode);
        }, 2000);
    });
});
Copy the code

The effect is as follows:

However, if you quickly click on a list element multiple times before the list item disappears, the console will have an exception:

The reason for the exception is that when the element is not gone, clicking again will respond to the event again, so executing the event handler will start multiple setTimeout timers. After the first timer is executed, the node has been removed, and other timers will report an error.

The solution is to have the click event program execute only once.

Specify the once parameter when adding event listeners

const list = document.querySelector('ul');
const buttons = list.querySelectorAll('button');
buttons.forEach((button) = > {
    button.addEventListener('click'.(evt) = > {
        const target = evt.currentTarget;
        target.parentNode.className = 'completed';
        setTimeout(() = > {
            list.removeChild(target.parentNode);
        }, 2000);
    }, { once: true });
});
Copy the code

Earlier browsers may not support the once parameter.

Use the removeEventListener method

Remove after the event is executed

const list = document.querySelector('ul');
const buttons = list.querySelectorAll('button');
buttons.forEach((button) = > {
    button.addEventListener('click'.function f(evt) {
        const target = evt.currentTarget;
        target.parentNode.className = 'completed';
        setTimeout(() = > {
            list.removeChild(target.parentNode);
        }, 2000);
        target.removeEventListener('click', f);
    });
});
Copy the code

Use the disabled attribute of the element

Disable the target element using the disabled attribute of the element to allow only one click:

const list = document.querySelector('ul');
const buttons = list.querySelectorAll('button');
buttons.forEach((button) = > {
    button.addEventListener('click'.function f(evt) {
        const target = evt.currentTarget;
        target.parentNode.className = 'completed';
        setTimeout(() = > {
            list.removeChild(target.parentNode);
        }, 2000);
        target.disabled = true;
    });
});
Copy the code

Other uses where event methods are executed only once. For example, when shopping cart data, or payment data is submitted to the server, you need to make sure that the button is not clicked again:

However, these solutions must be repeated in different requirements scenarios. Is there a general way to cover all requirements that only need to be executed once?

Function decorator — the once function

We need to separate “execute once” processes from functions. This process is called process abstraction.

Once the function:

function once(fn) {
  return function (. args) {
    if(fn) {
      const ret = fn.apply(this, args);
      fn = null;
      returnret; }}; }// this refers to the caller of the function, which calls function (... The args) objects
Copy the code

The argument to the once function, fn, is a function, the event handler. The return value of once is also a function. This return function is the “execute once” process abstraction. So call this return function a fn modifier, and call once a function decorator.

The implementation of this code: the event is fired, the first time fn’s modifier is called, fn exists, so fn is executed, then fn is set to NULL, and the result of fn’s execution is returned. When the modifier is executed again, fn is null and will not be executed again. Thus implementing a procedure that is only called once.

In this way, you can use it to fulfill the previous requirement:

const list = document.querySelector('ul');
const buttons = list.querySelectorAll('button');
buttons.forEach((button) = > {
  button.addEventListener('click', once((evt) = > {
    const target = evt.currentTarget;
    target.parentNode.className = 'completed';
    setTimeout(() = > {
        list.removeChild(target.parentNode);
    }, 2000);
  }));
});

// Requests for shopping cart data, order or payment data are submitted only once
formEl.addEventListener('submit', once((evt) = > {
  fetch('path/to/url', {
    method: 'POST'.body: JSON.stringify(formData),
    ...
  });
}));
Copy the code

So that will be executed only once “, after abstracting the process of both the event handler and form submission functions need to focus only on the business logic, and don’t need to add the target. The disabled = false or target. The removeEventListener non-business logical statements, etc.

In addition, you can extend the once method. For example, for more than one execution, give the execution processing method defined. Add a second callback to execute after more than one execution as follows:

function once(fn, replacer = null) {
  return function (. args) {
    if(fn) {
      const ret = fn.apply(this, args);
      fn = null;
      return ret;
    }
    if(replacer) { // Additional execution
      return replacer.apply(this, args); }}; }Copy the code

The following object initialization method can throw an exception if the user executes it multiple times.

const obj = {
  init: once(() = > {
    console.log('Initializer has been called.');
  }, () = > {
    throw new Error('This method should be called only once.');
  }),
}

obj.init();
obj.init();
Copy the code

Throttling and stabilization function decorators

“Throttling and shaking” is also a common function decorator

The throttle

Throttling is used to prevent frequent execution and limit the frequency of execution (such as sending data to the server).

  • Conventional implementation

For example, in the mouse movement event, we want to prevent the event function from executing frequently following the movement:

const panel = document.getElementById('panel');

let throttleTimer = null;
panel.addEventListener('mousemove'.(e) = > {
  if(! throttleTimer) {// If throttleTimer is null, perform the event operation

    // throttleTimer specifies a timer. The event function can be executed only when the throttleTimer is null
    throttleTimer = setTimeout(() = > {
      throttleTimer = null;
    }, 100); }});Copy the code

Throttling is achieved using this timer, but it is not universal. Each time you encounter a throttling problem, copy the code and modify it.

  • Throttling decorator method

The process of throttling can be abstracted into a general throttling decoration method:

function throttle(fn, ms = 100) {
  let throttleTimer = null;
  return function (. args) {
    if(! throttleTimer) {const ret = fn.apply(this, args);
      throttleTimer = setTimeout(() = > {
        throttleTimer = null;
      }, ms);
      returnret; }}; }Copy the code

The first argument to throttle is a function, and the return value is also a function, which returns a function decorated with the argument fn.

Thus, the throttling operation is implemented again:

const panel = document.getElementById('panel');
panel.addEventListener('mousemove', throttle((e) = > {
  // Event response operation
}));
Copy the code

If you specify the value Infinity to the second argument to the decorator function throttle, it is a once function.

Infinity is a property of the Global Object, i.e. it is a global variable. Infinity is greater than any value.

function throttle(fn, ms = 100) {
  let throttleTimer = null;
  return function(. args) {
    if(! throttleTimer) {const ret = fn.apply(this, args);
      throttleTimer = setTimeout(() = > {
        throttleTimer = null;
      }, ms);
      returnret; }}}function once(fn) {
  return throttle(fn, Infinity); // The throttle timer will never expire
}
Copy the code

Image stabilization

The debounce function is similar to throttling in that it cancellations (prevents) unnecessary execution except for the last one. That is, if the event is fired multiple times in a row (in a very short period of time), the stabilization function prevents multiple (repeated useless) executions, leaving only the last operation.

The most common application is the resize event on a form, which only runs when the user has stopped resizing the window.

Here is an example of drawing a canvas continuously under the window strip:

<div id="panel">
  <canvas></canvas>
</div>
Copy the code
html.body {
  width: 100%;
  height: 100%;
  padding: 0;
  margin: 0;
}
#panel {
  width: 100%;
  height: 0;
  padding-bottom: 100%;
}
Copy the code
const panel = document.getElementById('panel');
const canvas = document.querySelector('canvas');
function resize() {
  canvas.width = panel.clientWidth;
  canvas.height = panel.clientHeight;
}
function draw() {
  const context = canvas.getContext('2d');
  const radius = canvas.width / 2;
  context.save();
  context.translate(radius, radius);
  for(let i = radius; i >= 0; i -= 5) {
    context.fillStyle = `hsl(${i % 360}`, 50%, 50%);
    context.beginPath();
    context.arc(0.0, i, i, 0.Math.PI * 2);
    context.fill();
  }
  context.restore();
}

resize();
draw();

window.addEventListener('resize'.() = > {
  resize();
  draw();
});
Copy the code

Implement a circle of different colors on the Canvas and allow the size of the Canvas to be resized and drawn according to the width of the page. You can test that when you drag the width of the window, you can see that the Canvas circle is stuck.

When you drag and drop the window, the resize event is triggered repeatedly, and each time it is triggered, the Canvas is redrawn

ifi -= 5I can’t see the lag, so I can change it toI - = 0.1

The solution is that the user does not draw the canvas during the operation and only redraws the canvas after the last window size change. This process is called anti-shaking.

  • Conventional implementation
let debounceTimer = null;
window.addEventListener('resize'.() = > {
  if(debounceTimer) clearTimeout(debounceTimer); // If the alarm is triggered again before the scheduled end, all previous operations are cleared and corresponding operations are scheduled to be performed again.
  debounceTimer = setTimeout(() = > {
    resize();
    draw();
  }, 500);
});
Copy the code

As above, the drawing of the Canvas only takes place after the last operation, and the middle operation of Canvas drawing will not be triggered. Thus preventing jitter phenomenon.

  • Shake – proof decorator method
function debounce(fn, ms) {
  let debounceTimer = null;
  return function (. args) {
    if(debounceTimer) clearTimeout(debounceTimer);

    debounceTimer = setTimeout(() = > {
      fn.apply(this, args);
    }, ms);
  };
}
Copy the code

Shockproof is achieved as follows:

window.addEventListener('resize', debounce(() = > {
  resize();
  draw();
}, 500));
Copy the code
  • The difference between throttling and shaking

  • Throttling is to let the event handler fire after a specified millisecond. Limit the frequency of execution

  • In anti-shake mode, the intermediate operation is ignored and only the last operation is responded. To prevent multiple executions, only the last operation is retained

Functions whose arguments and return values are Functions are called High Ordered Functions. The once, debounce, and Throttle function decorators are all higher-order functions.

Function interceptor

Function interceptors are another application of higher-order functions.

Implementation of a function interceptor

In the case of a maintained tool library facing a major upgrade, some API will be changed or deprecated; However, many businesses or tools use this version, and it is not possible to upgrade or directly replace the old API. The transition must be smooth — instead of canceling the old API, add a prompt telling the calling user that the API is being deprecated and must be upgraded.

Use console.warn to output a prompt:

function deprecate(oldApi, newApi) {
  const message = `The ${oldApi} is deprecated.
                  Please use the ${newApi} instead.`;
  console.warn(message);
}
Copy the code

As in the Deprecate function. If an API is to be deprecated, modify the code as follows:

export function foo() {
  deprecate('foo'.'bar');
  // do sth...
}
Copy the code

Call foo, and the console will print a warning:

This is logically possible, but you need to find all the apis to be deprecated and manually add the Deprecate method to each one, which increases the risk of manual error and is a lot of work and tedious.

The best way to do this is to leave the original API unchanged and display a prompt before calling the deprecated API!!

// deprecation.js
// Introduce the API to be deprecated
import {foo, bar} from './foo'; .// Use a higher-order function
const _foo = deprecate(foo, 'foo'.'newFoo');
const _bar = deprecate(bar, 'bar'.'newBar');

// Re-export the modified API
export {
  foo: _foo,
  bar: _bar,
  ...
}
Copy the code

Import the API from the library to be deprecated into the Deprecation module. The deprecated methods are then thrown into the Deprecate sandbox, returning a decorated function and exporting the functions with the same name. When other users call these methods, they will go through the Deprecate sandbox, displaying a prompt, and then execute the contents of the Foo or bar methods.

The deprecate function is implemented as follows:

function deprecate(fn, oldApi, newApi) {
  const message = `The ${oldApi} is deprecated.
Please use the ${newApi} instead.`;
  const notice = once(console.warn);

  return function(. args) {
    notice(message);
    return fn.apply(this, args); }}Copy the code

Deprecate is also a higher-order function. It inputs a function fn and returns a function. Fn is the API to be deprecated. The returned function is a function that contains a print prompt and a fn call.

Note that notice = once(console.warn) is also defined with notice, so that calling the same function will only display a warning on the console once, avoiding the output of too many duplicate messages.

When we want to modify an API in the library, we can choose not to modify the code itself, but to modify the API, which can be abstracted as intercepting its input or output.

This is in line with the idea of interceptors in Web development. Based on this idea, a simple generic function interceptor can be designed:

function intercept(fn, {beforeCall = null, afterCall = null}) {
  return function (. args) {
    if(! beforeCall || beforeCall.call(this, args) ! = =false) { BeforeCall does not exist or returns false
      // If beforeCall returns false, no subsequent function is executed
      const ret = fn.apply(this, args);
      if(afterCall) return afterCall.call(this, ret);
      returnret; }}; }Copy the code

The intercept function is a high-order function that takes an object as its second argument. It provides two interceptor functions, beforeCall and afterCall, that “intercept” before and after the fn function.

In the pre-execution phase, fn can be prevented by returning false; In the later stages of execution, the return value of the fn function can be replaced by the return value of afterCall.

Function interceptorinterceptThe purpose of the

Monitor the execution of a function at any time, and obtain the execution information of the function without modifying the code:

function sum(. list) {
  return list.reduce((a, b) = > a + b);
}

sum = intercept(sum, {
  beforeCall(args) {
    console.log(`The argument is ${args}`);
    console.time('sum'); // Monitor performance
  },
  afterCall(ret) {
    console.log(`The resulte is ${ret}`);
    console.timeEnd('sum'); }}); sum(1.2.3.4.5);
Copy the code

Console. time: Start a timer to track the duration of an operation. Each timer must have a unique name, and a maximum of 10,000 timers can run simultaneously on a page. When console.timeend () is called with this timer name as an argument, the browser outputs the elapsed time of the corresponding timer in milliseconds.

console.time(timerName);

Adjust parameter order

As follows, use the interceptor to reposition the timer parameters to generate a new timer.

const mySetTimeout = intercept(setTimeout,  {
  beforeCall(args) {
    [args[0], args[1]] = [args[1], args[0]]. }}); mySetTimeout(1000.() = > {
  console.log('done');
});
Copy the code

Parameter types of the check function

const foo = intercept(foo, {
  beforeCall(args) {
    assert(typeof args[1= = ='string'); }});Copy the code

Function “purity,” testability, and maintainability

Batch functions and pure functions

The following two utility functions style elements:

export function setStyle(el, key, value) {
  el.style[key] = value;
}

export function setStyles(els, key, value) {
  els.forEach(el= > setStyle(el, key, value));
}
Copy the code

They have a common drawback — they all depend on the external environment (the parameter EL element) and change that environment.

If you want to black-box these two functions, you have to build test environments for them and create different DOM element structures, which certainly increases the cost of the toolkit tests. You need to improve the testability of the function

To improve the testability of a function, you need to improve the purity of the function, that is, reduce the dependence of the function on the external environment, and reduce the change of the function to the external environment. Such functions become pure functions.

A strictly pure function, deterministic, side-effect-free, idempotent. In other words, a pure function does not depend on the external environment, does not change the external environment, no matter how many times it is called, no matter when it is called, as long as the parameters are determined, the return value is determined. A function like this is a pure function.

Here’s a refactoring of two utility functions:

function batch(fn) {
  return function(subject, ... args) {
    if(Array.isArray(subject)) {
      return subject.map((s) = > {
        return fn.call(this, s, ... args); }); }return fn.call(this, subject, ...args);
  }
}

export const setStyle = batch((el, key, value) = > {
  el.style[key] = value;
});
Copy the code

The implementation is classic.

Higher-order functions are characterized by whether the input is a function or whether the return is a function. Input function is practical to perform the function of the operation or to complete the function, but the function is as a process is retained, the original complete the processing of the common parts of the code of spun off (or abstract), namely the abstract process, concrete business as a function of the code are preserved, which is passed in as the parameters of the higher-order functions function; Then, advanced function after finishing the process of abstraction, the decoration of the passed to the function (i.e., return function), the scope of the parameters, intact to the input function (that is, the actual finish operation specific function) and execute, and executed directly input function is the same effect (and abstracts the process of scene processing code), Therefore, the input function is written almost exactly as the code function intended to be implemented. In this way, we can wrap the original function without affecting it. This is where the function interceptor comes from. The implementations of Once, debounce, and Throttle come from pure process abstractions. Pure functions come from keeping all the code that affects the external environment, non-idempotent code, and abstracting the implementation of other processes.

The return function of a higher-order function is just a wrapper, carrying the abstracted process and connecting the parameters and scope of the original function.

Batch is a higher-order function, as shown in the code above. In its return function, the first parameter subject, if it is an array, iterates through fn with each element of that array as the first parameter, returning the result as an array. If subject is not an array, fn is called directly and the result is returned.

The post-batch setStyle function has the ability to operate on elements individually or in batches, equivalent to the combination of the original setStyle and setStyles.

For example, implement setting the font color of some elements to red:

const items = document.querySelectorAll('li:nth-child(2n+1)');

setStyle([...items], 'color'.'red');
Copy the code

After refactoring, batch is a pure function, although setStyle is still not a pure function. By reducing impure functions and increasing the number of pure functions in the library, the testability and maintainability of the library are improved.

Black-box testing the Batch function is as simple as passing parameters to the batch function to see if it returns the expected results, without building an HTML environment for it:

const list = [1.2.3.4];
const double = batch(num= > num * 2);

double(list); // 2, 4, 6, 8
Copy the code

The necessity of pure functions

With the help of the batch implementation above, this can be done simply by merging setStyle and setStyles:

function setStyle(el, key, value) {
  if(Array.isArray(el)) {
    return el.forEach((e) = > {
      setStyle(e, key, value);
    });
  }
  el.style[key] = value;
}
Copy the code

First of all, it breaks the principle of simple function responsibility (changing styles in JS). Second, there may be other similar functions in the toolkit, such as adding/removing calss state, so you need to redefine a setstyle-like function, such as:

function addState(el, state) {
  removeState(el, state);
  el.className = el.className ? `${el.className} ${state}` : state;
}

function removeState(el, state) {
  el.className = el.className.replace(new RegExp(`(^|\\s)${state}(\\s|$)`.'g'), ' ');
}

function addStates(els, state) {
  els.forEach(el= > addState(el, state));
}
Copy the code

If you want to change, you need to change these methods together to:

function addState(el, state) {
  if(Array.isArray(el)) {
    return el.forEach((e) = > {
      addState(e, state);
    });
  }
  removeState(el, state);
  el.className = el.className ? `${el.className} ${state}` : state;
}

function removeState(el, state) {
  if(Array.isArray(el)) {
    return el.forEach((e) = > {
      removeState(e, state);
    });
  }
  el.className = el.className.replace(new RegExp(`(^|\\s)${state}(\\s|$)`.'g'), ' ');
}
Copy the code

If we have the batch method, because const setStyle = batch(…) The function decorator is used to transform a function into a batch processing function, which does not violate the single responsibility principle when it is defined. When testing, as long as the correctness of the pure function batch is guaranteed, you do not need to worry about the correctness of the function after the batch transformation.

Also, it’s easy to modify the other functions. Batch decorates all functions that need to be batched:

// Uniform mass processing
addState = batch(addState);
removeState = batch(removeState);
Copy the code

Batch high-order functions are designed to increase pure functions and reduce impure functions, which greatly improves the testability and maintainability of the library. This is why we need to use higher-order function process abstractions to design and refactor libraries.

The batch higher-order function is used to change the first parameter from a single operation to a batch operation, and the subsequent parameters remain unchanged.

Normal forms of higher order functions

Understand the patterns of higher-order functions so that it is clear how to process abstract the original function and design more or more efficient higher-order functions.

Normal form of higher order functions:

function HOF0(fn) {
  return function(. args) {
    return fn.apply(this, args); }}Copy the code

HOF0 is an equivalent form of a higher-order function, or in other words, the function that HOF0 decorates does exactly the same thing that fn does. Because the modified function simply calls fn with the call’s this context and argument, and returns the result. In other words, executing HOF0 is exactly the same as executing FN directly.

function foo(. args) {
  // do anything.
}
const bar = HOF0(foo);

console.log(foo('something'), bar('something')); // Calling foo is equivalent to calling bar
Copy the code

HOF0 is the base form on which other function decorators are based, either to modify the parameters, such as batch, or to modify the returned results, such as once, Throttle, debounce, and batch.

Other higher-order functions can be designed on this basis.

For example, design a continuously executing function that executes recursively, similar to the Reduce method for arrays, but more flexible:

function continous(reducer) {
  return function (. args) {
    return args.reduce((a, b) = > reducer(a, b));
  };
}
Copy the code

Then, create a function that can recursively process the input:

const add = continous((a, b) = > a + b);
const multiply = continous((a, b) = > a * b);

console.log(add(1.2.3.4)); // 1 + 2 + 3 + 4 = 10

console.log(multiply(1.2.3.4.5)); // 1 * 2 * 3 * 4 * 5 = 120
Copy the code

Similar to batch, continous can also be used to create methods that operate on elements in batches, but the parameters and usage need to be adjusted:

const setStyle = continous(([key, value], el) = > {
  el.style[key] = value;
  return [key, value]; // Make the first argument of the recursive call always [key, value] by returning. Implement recursive batch modification of EL
});

const list = document.querySelectorAll('li:nth-child(2n+1)');
setStyle(['color'.'red'], ...list);  // continous recursively iterates, expanding the list and passing setStyle

Copy the code

If you want to use list as an argument instead of passing… List, you can implement a higher-order function to handle it (that is, to handle the list expansion) :

function foldLastParameter(fn) {
  return function (. args) {
    const lastArg = args[args.length - 1];
    if(lastArg.length) {
      return fn.call(this. args.slice(0, -1), ...lastArg); // If the last argument is an array, expand
    }
    return fn.call(this. args); }; }Copy the code

The foldLastParameter function determines that the last argument is an array or array-like (such as NodeList), and then expands it to fn (which folds the argument relative to the modified function, so the higher-order function is named fold).

Originally, I thought that I could modify the continous function, so that I could not expand the list. However, by doing so, I broke the structure of the decorative function returned by the original continous, and modified the code processing. And the logic processing is no longer recursive execution of the function, destroying the integrity of the function and the original function.

By adding a layer of decorators, without modifying the original function, the expansion function can be handled, which reflects the function of decorators and the embodiment of the abstract process.

Moon shadow big guy ox!

Change setStyle again:

// Add a foldLastParameter to the continous list. Carried out.
const setStyle = foldLastParameter(continous(([key, value], el) = > {
  el.style[key] = value;
  return [key, value];
}));

const list = document.querySelectorAll('li:nth-child(2n+1)');

setStyle(['color'.'red'], list);
Copy the code

The foldLastParameter here is suitable for passing two parameters, because only the last parameter is expanded in foldLastParameter, and the final setStyle modification style implementation needs to make sure that the first parameter is the style and the second is the element. Unless the implementation expands all parameters except the first,

Then, adjust the parameter order so that setStyle is closer to the batch version:

function reverse(fn) {
  return function (. args) {
    return fn.apply(this, args.reverse());
  };
}
Copy the code

The higher-order reverse function reverses the order in which the arguments are called:

const setStyle = reverse(foldLastParameter(continous(([key, value], el) = > {
  el.style[key] = value;
  return [key, value];
})));

const list = document.querySelectorAll('li:nth-child(2n+1)');

setStyle(list, ['color'.'red']);  // The setStyle argument is changed to list and ['color','red']
Copy the code

Then, you can expand the arguments [‘color’, ‘red’], and you need to implement a higher-order function of spread that is the opposite of fold:

function spread(fn) {
  return function (first, ... rest) {
    return fn.call(this, first, rest);
  };
}
Copy the code

The final setStyle method can be the same as the previous batch method:

const setStyle = spread(reverse(foldLastParameter(continous(([key, value], el) = > {
  el.style[key] = value;
  return [key, value];
}))));

const list = document.querySelectorAll('li:nth-child(2n+1)');

setStyle(list, 'color'.'red');
Copy the code

Spread (Reverse (foldLastParameter(continous(…)) )), which has the effect of implementing a batch higher-order function

In this case, it is equivalent to:

// The four-layer decorator implements batch
function batch(fn) {
  return spread(reverse(fold(continous(fn))));
}

const setStyle = batch(setStyle);
Copy the code

The difference between the original batch function and the original batch function is that the parameters of the original function are in a different order and the return value is required:

// This is the original function
([key, value], el) => {
  el.style[key] = value;
  return [key, value];
}
Copy the code

Higher-order functions can be combined to create more powerful functions.

A similar spread (reverse (a fold (continous…). ), which can also be adapted to a friendlier form using higher-order functions:

// Make a nested call with a list
function pipe(. fns) {
  return function(input) {
    return fns.reduce((a, b) = > {
      return b.call(this, a); }, input); }}Copy the code

The higher-order function pipe takes a list of functions and returns a function that iterates over the list of functions with input and returns the final result.

Such as:

const double = (x) = > x * 2;
const half = (x) = > x / 2;
const pow2 = (x) = > x ** 2;

const cacl = pipe(double, pow2, half);
const result = cacl(10); // (10 * 2) ** 2/2 = 200
Copy the code

A pipe is like a pipe in which the input data is sequentially passed through a series of functors to get the final output. This model is also the basic model for functional programming, where higher-order functions are the basis.

Batch can be represented by pipe instead of the previous higher-order functions:

const batch = pipe(continous, fold, reverse, spread);
Copy the code

const pipe = continous((prev, next) = > {
  return function(input) {
    return next.call(this, prev.call(this, input)); }});Copy the code

Pipe is a function wrapped with continue that takes a list of function arguments and iterates over each function

The result of pipe(m1,m2,m3) is the result of the iteration, which is the function pipe_iter_func

Pipe_iter_func is passed as an argument to execute

The key is to understand the “outcome of the iteration.”

Pipe is used to nest the execution of each function list item

(prev, next) => The result returned by the arrow function is also a function that can be used to execute the passed prev, next. This function also acts as the prev for the next iteration and is nested in the next iteration’s execution parameters.

During iteration, the input parameter input of return function(input) is used only to pass to the last function, and the code implementation of function(input) is used to form the next function nested within the previous function execution. Except for the function(input) returned on the last iteration, that input is passed in later pipe_iter_func executions. This input is then passed to the first function. And execute each function nested.

The key to understanding “results of iterations” is to understand what input does.

It’s so hard to read, you can only work backwards step by step, and it feels almost impossible to design and implement it yourself (creating higher order functions related to it)…

The power of higher order function combination!!

This article is participating in the “Gold Nugget Booklet” for free! Event, click to view the event details