Participated in the byte youth training camp activities, saw the legendary moon shadow teacher, the key also listened to him give us two how to write good JS class! It’s so profitable. Today, I will share what I have learned in class and study with you! ~

Yue Ying teaches us three principles for writing good JavaScript (including other languages) : ① Responsibility for each component; ② component encapsulation; and ③ process abstraction

Today we’ll look at the final principle of writing good JavaScript — procedural abstraction

Start 0.

The two principles we learned earlier are both about abstracting data, so to speak, by abstracting our data into an array of objects or objects that we pass to our plug-in or component constructor.

The next thing we want to talk about is abstracting the process.

Process abstraction is a basic application of functional programming ideas

Look at this interesting picture

The process of opening a door (action), we can abstract, because there are many places can use this process (action).

As before, we’ll learn by example

1. Case introduction

Limit operation

We often need to limit the number of operations, such as asynchronous interactions, one-time HTTP requests

Let’s look at a specific requirement: let the user check the task, and the task will slowly disappear

const list = document.querySelector('ul'); 
const buttons = list.querySelectorAll('button'); 
buttons.forEach((button) = > { 
    // We bind click events to the button
    button.addEventListener('click'.(evt) = > { 
        const target = evt.target; 
        // Change the style of the currently clicked element and fade away
        target.parentNode.className = 'completed'; 
        // Delete the element after two seconds
        setTimeout(() = > { 
            list.removeChild(target.parentNode); 
        }, 2000); 
    }); 
});
Copy the code

The effect can be rendered, but there is a problem. If we click the same button many times quickly, we will get the following error

The element doesn’t disappear, and when we click, the event it’s bound to will still be triggered, causing multiple removechilds, so an error will be reported

So how do you solve this problem? We can make the bound event execute only on the first click and not on any subsequent clicks. That is, we want the bound event to be “executed once”.

We can set the once parameter for addEventListener (IE can’t)

We can also add removeEventListener to the callback function to remove the bound event after the first execution

Although the above method can achieve “one execution”, but in the original code to do the modification, some compatibility problems, and is not easy to expand.

To enable the “once” requirement to cover different event handling, we can separate this requirement out. This process is called process abstraction

Once Executes the function Once

Execute the function Once Once

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

The once function takes fn as an argument and returns a new function. In the return function, one thing is that fn is only executed once, and the second time it is executed, fn has been assigned a value of null, so it cannot be executed again.

Let’s use the once function

button.addEventListener('click', once((evt) = > { 
    const target = evt.target; 
    target.parentNode.className = 'completed'; 
    setTimeout(() = > { 
        list.removeChild(target.parentNode); 
    }, 2000); 
}));
Copy the code

Any function that needs to be executed once can be implemented by wrapping a layer of once around it. We can call once a function decorator. What is a function decorator?

2. Higher-order functions

define

Let’s look at the definition of higher-order functions

Functions that take functions as arguments or return values are higher-order functions

Once is a higher-order function

Functions that satisfy both conditions are often used as function decorators

expand

Which apis for arrays in JS are higher-order functions?

【答 案 】every, map, filter, forEach, reduce, sort

HOF0 equivalent normal form

HOF0 is the equivalent normal form of higher-order functions

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

Fn and HOF0(fn) are completely equivalent, regardless of how the parameters, call context changes, they are equivalent! That is, there is no difference between executing fn and HOF0(fn)

It can be seen that our Once function is expanded on HOF0

Common higher-order functions

In addition to the Once function, there are many other commonly used higher-order functions

Throttle function

① Define the throttling function

Triggers multiple events evenly distributed over time

function throttle(fn, time = 500){
  let timer;
  return function(. args){
    if(timer == null){
      fn.apply(this,  args);
      timer = setTimeout(() = > {
        timer = null;
      }, time)
    }
  }
}
Copy the code

② Use the throttling function, just need to wrap the function can be used

btn.onclick = throttle(function(e){
  circle.innerHTML = parseInt(circle.innerHTML) + 1;
  circle.className = 'fade';
  setTimeout(() = > circle.className = ' '.250);
});
Copy the code

③ Effect, consecutive clicks will only be recorded once every 500ms

Debounce function

(1) Define the anti-shake function

Suitable for multiple events with one response

function debounce(fn, time = 100){
  var timer;
  return function(){
    clearTimeout(timer);
    timer = setTimeout(() = > {
      fn.apply(this.arguments); }, time); }}Copy the code

② Use the anti – shake function

We want the bird to follow the mouse, but not all the time, but when the mouse is set there the bird starts to move in a straight line

var i = 0;
// Let the bird flap its wings
setInterval(function(){
  bird.className = "sprite " + 'bird' + ((i++) % 3);
}, 1000/10);

// Use the anti-shake function
document.addEventListener('mousemove', debounce(function(evt){
  var x = evt.clientX,
      y = evt.clientY,
      x0 = bird.offsetLeft,
      y0 = bird.offsetTop;
  
  console.log(x, y);
  
  var a1 = new Animator(1000.function(ep){
    bird.style.top = y0 + ep * (y - y0) + 'px';
    bird.style.left = x0 + ep * (x - x0) + 'px';
  }, p= > p * p);
  
  a1.animate();
}, 100));
Copy the code

(3) the effect

For more on throttling and stabilization, you can read my previous blog [JS] function Throttling and Stabilization

Consumer function

① Define the consumer function

In this case, the consumer function turns a synchronous operation into an asynchronous one

function consumer(fn, time){
  let tasks = [],
      timer;
  
  return function(. args){
    tasks.push(fn.bind(this. args));if(timer == null){
      timer = setInterval(() = > {
        tasks.shift().call(this)
        if(tasks.length <= 0) {clearInterval(timer);
          timer = null;
        }
      }, time)
    }
  }
}
Copy the code

② Usage 1 gradually add up

The consumerAdd is the equivalent of a consumerAdd, so that you can achieve an asynchronous effect, executing an Add every second

function add(ref, x){
  const v = ref.value + x;
  console.log(`${ref.value} + ${x} = ${v}`);
  ref.value = v;
  return ref;
}

let consumerAdd = consumer(add, 1000);

const ref = {value: 0};
for(let i = 0; i < 10; i++){
  consumerAdd(ref, i);
}
Copy the code

(3) the effect

② Usage 2 Asynchronous increase of combos (fast click slow execution)

btn.onclick = consumer((evt) = >{
  let t = parseInt(count.innerHTML.slice(1)) + 1;
  count.innerHTML = ` +${t}`;
  count.className = 'hit';
  let r = t * 7 % 256,
      g = t * 17 % 128,
      b = t * 31 % 128;
  
  count.style.color = `rgb(${r}.${g}.${b}) `.trim();
  setTimeout(() = >{
    count.className = 'hide';
  }, 500);
}, 800)
Copy the code

(3) the effect

Quick click and execute slowly

Iterative function

① Define iterator functions

This allows us to wrap an operation function into an operation that can be used iteratively

function iterative(fn) {
  return function(subject, ... rest) {
    // If the object is an iterable, iterate over the subobject
    if(isIterable(subject)) {
      const ret = [];
      for(let obj of subject) {
        ret.push(fn.apply(this, [obj, ...rest]));
      }
      return ret;
    }
    return fn.apply(this, [subject, ...rest]); }}Copy the code

(2) use

The color change is a one-step operation, and after the iterative decoration is passed in, all elements in the iterative object are iterated for operation

const isIterable = obj= >obj ! =null && typeof obj[Symbol.iterator] === 'function';
  
const setColor = iterative((el, color) = > {
  el.style.color = color;
});

const els = document.querySelectorAll('li:nth-child(2n+1)');
setColor(els, 'red');
Copy the code

(3) the effect

Interception function

In addition to function modifiers, you can also define function interceptors

For example, if we have a library and a function in it is no longer recommended, we should not modify our original code directly in the library. We should intercept the function in the library and define a deprecate interceptor function

define

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

use

Instead of modifying the code itself, you modify the API, which can be abstracted to intercept its input or output.

// Introduce the deprecated API
import {foo, bar} from './foo'; 

// Deprecate with the interceptor 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

3. Pure functions

So with all the higher-order functions, why do we use higher-order functions?

Before we explain this problem, let’s introduce the concept of pure functions

define

A strictly pure function is deterministic, without side effects and idempotent. That is, a pure function does not depend on the external environment, nor does it change the external environment, no matter how many times it is called, no matter when it is called, as long as the argument is determined, the return value is determined. Functions like this are pure functions.

Let’s look at an example

Pure functions

function add(a, b) {
    return a + b;
}
add(1.2) / / 3
add(1.2) / / 3
Copy the code

Every time I do it, I get the same result, and I get the same result when I change the order

There are pure functions and there are impure functions, so let’s look at an example of an impure function

let x = 10
function foo() {
    // Changes the function context data x
    return x++
}
function bar() {
    return x * 10
} 

foo() / / 11
bar() / / 110
bar() / / 1100
foo() / / 1101
Copy the code

Every time I do it, I get a different result, and I get a different result when I change the order

It can be seen that the higher-order functions developed through HOF0 equivalent paradigm are pure functions

Advantages of pure functions

testability

When we’re unit testing, if it’s a pure function, we can test it without context,

If it’s a non-pure function, we need to build its context

So the best practice is to write more pure functions!!

expand

Which array apis in JS are pure functions?

【 pure 】concat, map, filter, slice

【 impure 】push, pop, shift, unshift, forEach, some, every, reduce

4. Programming paradigm

classification

Imperative concerns How to do (FPRTRAN, C, C++, Java)

Declarative concerns What to do (Prolog, Haskell, Erlang)

Our JavaScript can write either imperative or declarative code

When dealing with complex logic, it is recommended to use declarative, which is more abstract and extensible

Let’s look at an example

Toggle case

imperative

switcher.onclick = function(evt){
  if(evt.target.className === 'on'){
    evt.target.className = 'off';
  }else{
    evt.target.className = 'on'; }}Copy the code

declarative

function toggle(. actions){
  return function(. args){
    let action = actions.shift();
    actions.push(action);
    return action.apply(this, args);
  }
}

switcher.onclick = toggle(
  evt= > evt.target.className = 'off'.evt= > evt.target.className = 'on'
);
Copy the code

Declarative seems more cumbersome, but if you want to add another state to the switch, declarative is very handy

Declarative encoding is more extensible

function toggle(. actions){
  return function(. args){
    let action = actions.shift();
    actions.push(action);
    return action.apply(this, args);
  }
}

switcher.onclick = toggle(
  evt= > evt.target.className = 'warn'.evt= > evt.target.className = 'off'.evt= > evt.target.className = 'on'
);
Copy the code

5. To summarize

Process abstraction/HOF/decorator

  • You can abstract not only the data, but also the process
  • Operations on functions can be abstracted with higher-order functions, which are easy to reuse and do not modify the original function code (non-invasive)
  • Use pure functions in your code base

Imperative/declarative

  • JavaScript can write both imperative and declarative code.
  • Write more declarative code, which is more abstract and extensible