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
-
Functional first-class citizen functions can be stored in variables with functions as arguments and functions as return values
-
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
-
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
-
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