1. Why learn functional programming

  • As React became more popular, it received more attention
  • Vue3 is also embracing functional programming
  • Functional programming can discard this
  • The packaging process makes better use of Tree Shaking to filter out useless code
  • Convenient for testing and parallel processing
  • There are many libraries that help us with functional development: Lodash, underscore, Ramda

2. What is functional programming

  • Object orientation: Abstracting things in the real world into classes and objects in the program world, demonstrating the relationship of things through encapsulation, inheritance, and polymorphism
  • Functional programming: Abstracts the budgeting process, encapsulating data as input and output streams

The definition is too abstract, so let’s look at functional programming in several ways

  1. Functional first-class citizen functions can be stored in variables with functions as arguments and functions as return values

  2. A higher-order function can pass a function as an argument to another function and return a function as a result of another function

支那

3. Take functions as arguments

Examples include array.prototype. map, array.prototype. filter and array.prototype. reduce, which take a function as an argument and apply that function to each element of the Array

Simulate the implementation of map Filter Reduce method in array

支那

1. Map: returns a new array with the same length as the original array

Array.prototype.map = function( callback ) { var obj = Object(this) var len = obj.length var arr = new Array(len) for(let key in obj) { arr[key] = callback(obj[key], key, Var arr = [1,2,3] var newArr = arr.map(item => {return item + 1}) // [2,3,4]Copy the code

2. Filter processes the condition in callback and returns the array that meets the condition

Array.prototype.filter = function( callback ) { var obj = Object(this) var len = obj.length var arr = new Array() for(let key in obj) { let result = callback(obj[key], key, Obj) if(result) {arr.push(obj[key])}} return arr} var arr = [1,2,3] arr.filter((item) => {return item > 1 }) / / [2, 3]Copy the code

3. Reduce receives two arguments, callback initValue. If initValue is passed, the default value is initValue, and if no initial value is passed, the default value is the first item in the array

Array.prototype.reduce = function( callback, initValue ) { var obj = Object(this) var accumulator var k = 0 if( initValue ) { accumulator = initValue }else{ if( obj.length ) { accumulator = obj[0] k++ }else{ throw new Error('empty array with no initial value') } } while( k < obj.length ) { accumulator = callback( accumulator, obj[k], k, Obj) k++} return accumulator} var arr = [1,2,3] var newArr = arr. Reduce ((accu,item, index) => {console.log(accu, item, index) item, index); return accu + item }) console.log(newArr); // 1,2,1/3,3,2 // 6Copy the code

4. The function is output as a return value

For example, a function that is executed only once

function once(fn){ var done = false return function() { if(! done) { done = true return fn.apply(this, arguments) } } } var ok = once(function() {}) function isType( type ) { return ( obj ) => { return Object. The prototype. The call (obj) = = = '[Object]' + type + ' '}} isType (' String ') (' 123 ') / / true isType (' Array ') / / ([1, 2, 3]) trueCopy the code

The above example is a higher-order function, and isType returns a function

Add (1)(2)(3)(4)…

As you can see, both take the function as the return value and receive the new argument

function add(a) {
    function sum(b) {
        a = a + b
        return sum
    }
    sum.toString = function() {
        return a
    }
    return sum
}
Copy the code

The print function automatically calls toString(), so just override sum.tostring to return a

5. Pure functions

Have the same input, always have the same output, without any observable side effects

Side effects include

  • Print function
  • The HTTP request
  • DOM query

In general, interactions with the external environment are side effects

The pure function has a couple of points

  1. As a cache

    var memorize = function(fn) { var cache = {} return function(... args) { var key = JSON.stringify(args) cache[key] = cache[key] || fn.apply(fn, ... Args) return cache[key]}} var m = memorize(funciton(x){x}) m(2) m(2) m(2Copy the code
  2. Better migration does not depend on external environment changes, so there is better migration

6. Function correlates

Function: multi-dimensional function into one-dimensional function, convenient function combination

When a function has more than one argument, it is called by passing some of the arguments (these arguments never change).

It then returns a new function that takes the remaining arguments and returns the result

For example, wrap an Add function

// Function add(a,b,c) {return a + b + c} // Function add(a) {return function(b) {return function(c) { Add (1,2,3) add(1)(2)(3)Copy the code

In the above example, we can see that the function parameters are collected first and the function returned at the innermost layer is processed

If you want to do regular verification of mobile phone number and email is the simplest method

Function validatePhone(phone) {return reg.test(phone)} function validataEmail(email) {return reg.test(email) }Copy the code

But if you add validation to other messages, you have to rewrap an validation function, so we might as well do that

function check(string, reg) {
    return reg.test(string)
}
check(phoneReg, '12345')
check(emailReg, '[email protected]')
Copy the code

This encapsulation is a little more complicated, as each validation requires the input of the re and string, in which case it is encapsulated by corrification

function check(reg) { return function(string) { return reg.test(string) } } var checkPhone = check(phoneReg) var CheckEmail = check(emailReg) // So when used checkPhone('12345') checkEamil('[email protected]')Copy the code

This will be very simple to use, so let’s encapsulate a generic coiling function

Function: creates a function that takes one or more parameters from fn, executes fn if all the parameters required by FN are provided, and returns the result of the execution, otherwise returns the function and waits for the remaining parameters to be received

function createCurry(fn, length) { length = length || fn.length return function(... args) { return args.length >= length ? fn.apply(this, args) : createCurry(fn.bind(this, ... args), length - args.length) } }Copy the code

Test an Add function

Var add = createCurry (function (a, b, c) {the console. The log ((a, b, c))}) add (1, 2, 3) add (1) (2) (3) the add (1, 2) (3) the add (1) (2, 3)Copy the code

The above results are the same

7. Partial functions

Locally fix some parameters of a function and then produce another function with smaller elements, which, unlike corrification, converts an n-element function into an n-x element function

function partial(fn, ... args) { return function(... newArgs) { return fn.apply(this, args.concat(newArgs)) } } var add = partial(function(a,b){ console.log(a+b) },1) add(2) // 3Copy the code

The main use of function correlates and partial functions is in function combinations

8. Anti-shake and throttle

Anti-shake and throttling

1. The throttle

Throttling functions are suitable for scenarios where functions are frequently called, such as window.onresize(), mousemove events, Scroll, and so on

There are two general implementation schemes

  • The first is to use the timestamp to determine whether the time difference has reached the execution time, if so, execute, and update the timestamp, and loop
  • The second is to use a timer. If the timer already exists, the callback is not executed until the timer is triggered, and then the timer is reset

1. Timestamp mode, record a timestamp, than using closure to save the variable, exceed the waiting time to execute the function

function throttle(fn, wait=50){
    var timeStamp = 0
    return function() {
        var now = new Date().getTime()
        if( now - timeStamp > wait ) {
            timeStamp = now
            fn()
        }
    }
}
Copy the code

1 For example, use Mousemove

Var hand = throttle (hanlder, 1000) function hanlder () {the console. The log (' I performed ')} the document. The addEventListener (" mousemove ", hand)Copy the code

This controls the mouse slide, executes the callback once every second, and implements a simple throttling function

2. If you

The anti-shake function refers to that no matter how many times a function is triggered in a certain period of time, it will only be executed for the last time, such as button clicking on the page, form submission, etc., all need to be anti-shake.

The implementation method is to use timers. When the function is executed for the first time, a timer is set, and when it is called, it will empty the previous timer and reset a timer. If there is a timer that has not been emptied, the execution will be triggered when the timer finishes

function debounce(fn, wait = 200) {
    var timer = null
    return function() {
        if( timer ) {
            clearTimeout(timer)
            timer = null
        }
        timer = setTimeout(() =>{
            fn()
        }, wait)
    }
}
Copy the code

Scroll event trigger

The function handler () {the console. The log (' I triggered ')} var hand = debounce (handler, 300) document. The addEventListener (' scroll 'hand)Copy the code

The effect is that the last handler is executed after the scroll has finished, and the other multiple triggers are not executed

3. Enhanced throttling

The problem with the throttling function just now is that the callback function was not executed for the last time after the mouse stopped, which may lead to the incorrect mouse position calculation. Therefore, according to the anti-shake function, it can be used in the throttling function to ensure the last function execution

function throttle(fn, wait = 50) { var timeStamp = 0, timer = null return function() { var now = new Date().getTime() if(now - timeStamp > wait) { timeStamp = now fn() }else{ If (timer) {clearTimeout(timer) timer = null} timer = setTimeout(()=>{timeStamp = now fn()}, wait) } } }Copy the code

9. Composition of functions

Pure functions and function corizations are easy to write onion code H (g(f(x)). Function composition allows us to recombine fine-grained functions to generate a new function

Data pipeline

Input a — — — — — — — — — — — — — — — — — — — > fn — — — — — — — — — — — — — — — — — — — — — — — — — – > b output

When f sub n is complicated, we can divide f sub n into several smaller functions, which become

Input a — — — — — — — — — — — — — — — — the f3 — — — — — — m — — — — > f2 — — — — — — — — — — — — — — — — — — — — — — — — — — > f1 – > b output

Function combination

fn = compose(f1, f2, f3 )
b = fn(a)
Copy the code

Compose combines f1,f2, and f3 into a new function, fn, and then performs the FN function composition: If a function needs to be processed by multiple functions to reach its final value, it can combine the intermediate functions into a single function

Functions are like conduits of data, and function composition connects these conduits, passing data through multiple conduits to form the final result. Function composition is executed from right to left by default

function compose(... args) { return function(value) { return args.reverse().reduce(function(val, cur) { return cur(val) }, value) } }Copy the code

The combination of functions satisfies the associative law,

We can either combine g with H, or f with g, and it’s the same thing

let a = compose(f,g,h)
let b = compose(compose(f,g), h) == compose(f, compose(g,h))
Copy the code

10. Functor Functor

Container: Can be thought of as a box, that is, an object that can store different values inside, exposing interfaces to manipulate the values inside

class Container{
    static of(value) {
        return new Container(value)
    }
    constructor(value) {
        this._value = value
    }
    map(fn) {
        return Container.of(fn(this._value))
    }
}

new Container(5)
.map(x => x + 1)
.map(x => x * x)
// 返回一个Container对象 { _value: 36 }
Copy the code

Functional programming typically does not withdraw the new keyword, so encapsulate new Container as a static method of

Container.of(5)
.map( x => x + 1)
.map( x => x * x)
Copy the code
  • Functional programming operations do not operate directly on values, but are performed by functors
  • A functor is an object that implements the Map contract
  • We can think of a functor as a box that encapsulates a value
  • To process the box’s value, we need to pass a value-handling function to the box’s Map method, which will process the value
  • The map method returns a box (functor) containing the new value

The above example implements a simple chain call

MayBe functor

We encounter many errors during programming and need to deal with them accordingly

The MayBe functor is used to handle external null cases by modifying the Map method

map(fn) {
    return this._value == null ? Container.of(null) : Container.of(this._value)
}
Copy the code

The MayBe functor handles the null case, but doesn’t tell the map about the null. Either functor can give useful hints

Either functor

Either of the two, similar to if else

Exceptions make functions impure, and Either functors can be used for exception handling

class Left {
    static of(value) {
        new Left(value) 
    }
    constructor(value) {
        this._value = value
    }
    map(fn) {
        return this
    }
}
class Right {
    static of(value) {
        new Right(value)     }
    constructor(value) {
        this._value = value
    }
    map(fn) {
        return fn(this._value)
    }}

function parseJSON(str) {
    var parse = JSON.parse(str)
    try{
        return Right.of(parse)
    }catch(e){
        return Left.of({err: e.message})
    }
}

parseJSON('{"name": "123"}')
.map( x => x.name.toUpperCase())
Copy the code

At this time, it is determined that the exception can be handled in this way, and the exception information is recorded

IO functor

  • The _value in the IO functor is a function, which is treated as a value
  • IO functors can store impure actions in _value, delay execution of the impure operation, and wrap the current operation
  • Leave impure operations to the caller

Task functor

Asynchronous tasks

Pointed functor

Functors that implement static methods of

The of method is used to avoid using new to create objects. The deeper meaning of the of method is to put values into Context

Monad functor

It’s a 50% functor that flattens IO(IO(x))

A functor is a monad if it has both join and of methods and obeies some laws