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