1.函数式编程优势 #

2.什么是函数式编程 #

//面向过程
let a=1;
let b=2;
let result = a+b;


//面向对象
class Sum{
    add(a,b){
        return a+b;
    }
}
let sum = new Sum();
sum.add(1,2);

//函数式编程,这里的函数指的是一种映射关系  y=f(x)

function add(a,b){
    return a+b;
}
add(1,2);

3.First-class Function(头等函数) #

function add(a,b){
    return a+b;
}
//1.函数可以赋值给变量
let add1 = add;

//2.函数可以作为参数
function exec(fn,a,b){
    fn(a,b);
}

//3.函数可以作为返回值
function exec(fn){
    return function(a,b){
        return fn(a,b);
    }
}

4.闭包(closure) #

function init() {
    var name = "hello";
    function showName() {
        debugger
        console.log(name);
    }
    return showName;
}
let showName = init();
showName();

20210731161528_1627719351356

5. 纯函数 #

5.1 什么是纯函数 #

function add(a,b){
    return a+b;
}

let c = 1;
let d =2;
function add2(a,b){
    d++;//修改了外部变量
    return a+b+c;//计算结果依赖外部变量
}
add2();
console.log(d);

5.2 优点 #

5.2.1 可缓存 #

cnpm i lodash -S
let _ = require('lodash');
const add = (a, b) => {
    console.log('add');
    return a + b;
}
const resolver = (...args)=>JSON.stringify(args)

var memoize = function (func,resolver) {
    let cache = {};
    let memoized =  (...args) => {
        const key = resolver?resolver(...args):JSON.stringify(args);
        if (typeof cache[key] !== 'undefined') {
            return cache[key];
        } else {
            cache[key] = func(...args);
            return cache[key];
        }
    }
    memoized.cache = cache;
    return memoized;
};

//let memoizedAdd = _.memoize(add,resolver);
let memoizedAdd = memoize(add,resolver);
console.log(memoizedAdd(1,2));
console.log(memoizedAdd(1,2));
console.log(memoizedAdd.cache);
module.exports = memoizedAdd;

5.2.2 可测试 #

cnpm install jest --save-dev

5.test.js

const sum = require('./5.js');
test('adds 1 + 2 to equal 3', () => {
  expect(sum(1, 2)).toBe(3);
  expect(sum(1, 2)).toBe(3);
});
{
  "scripts": {
    "test": "jest"
  }
}

6. 柯里化 #

let _ = require('lodash');
function add(a, b, c) {
    return a + b + c;
}
function curry(func) {
    let curried = (...args) => {
        if(args.length < func.length){
            return (...rest)=>curried(...args,...rest);
        }
        return func(...args);
    }
    return curried;
}
let curriedAdd = curry(add);
console.log(curriedAdd(1, 2, 3));
console.log(curriedAdd(1)(2, 3));
console.log(curriedAdd(1)(2)(3));

7. 组合 #

compose_1627620541868

手工组合

function add1(str) {
    return str + 1;
}
function add2(str) {
    return str + 2;
}
function add3(str) {
    return str + 3;
}
//手工组合
console.log(add3(add2(add1('hello'))));

flow

//let {flow} = require('lodash');
function add1(str) {
    return str + 1;
}
function add2(str) {
    return str + 2;
}
function add3(str) {
    return str + 3;
}
//手工组合
function flow(...fns) {
    if (fns.length == 0)
        return fns[0];
    return fns.reduceRight((a, b) => (...args) => a(b(...args)));
}
let flowed = flow(add3, add2, add1);
console.log(flowed('zhufeng'));

flowRight

let {flowRight} = require('lodash');
function add1(str) {
    return str + 1;
}
function add2(str) {
    return str + 2;
}
function add3(str) {
    return str + 3;
}
function flowRight(...fns) {
    if (fns.length == 0)
        return fns[0];
    return fns.reduce((a, b) => (...args) => a(b(...args)));
}
let flowed = flowRight(add3, add2, add1);
console.log(flowed('zhufeng'));

//带参数的函数组合

let {split,toUpper,join} = require('lodash');
let str = 'click button';//CLICK_BUTTON

let r1 = split(str,' ');
console.log(r1);//[ 'click', 'button' ]
let r2 = toUpper(r1);
console.log(r2);//CLICK,BUTTON
let r3 = split(r2,',');
console.log(r3);//[ 'CLICK', 'BUTTON' ]
let r4 = join(r3,'_');
console.log(r4);//CLICK_BUTTON

数据先放

let {split,toUpper,join,flowRight} = require('lodash');
let str = 'click button';//CLICK_BUTTON
//loadsh数据放前面
const func = flowRight(join('_'),split(','), toUpper, split(' '));
console.log(func(str));

数据后放

let {split,toUpper,join,flowRight} = require('lodash/fp');
let str = 'click button';//CLICK_BUTTON
//fp数据放后
const func = flowRight(join('_'),split(','), toUpper, split(' '));
console.log(func(str));

过程跟踪

let {split,toUpper,join,flowRight} = require('lodash/fp');
let str = 'click button';//CLICK_BUTTON
const logger = (name) => value => {
    console.log(name, value);
    return value;
}
const func = flowRight(join('_'),logger('afterSplit'),split(','),logger('afterToUpper'), toUpper, split(' '));
console.log(func(str));

8. Pointfree #

const { compose } = require("lodash/fp");
let num = 1;
//Pointed
function calcu(num){
    return num+1+2+3;
}

function add1(num){
    return num+1
}
function add2(num){
    return num+2
}
function add3(num){
    return num+3
}
//Pointfree
let fn = compose(add3,add2,add1);
console.log(fn(1));

9. 函子 #

9.1 Context #

class Container{
    constructor(value){
        this.value = value;
    }
}

9.2 Pointed Container #

class PointedContainer{
    constructor(value){
        this.value = value;
    }
    static of (value){
        return new PointedContainer(value);
    }
}

9.3 Functor #

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

let result = Functor.of('a')
.map(x=>x+1)
.map(x=>x+2)
.map(x=>x+3);
console.log(result.value);

9.4 Maybe #

class Maybe  {
    constructor(value){
        this.value = value;
    }
    static of (value){
        return new Maybe(value);
    }
    map(fn){
        return  this.value?new Maybe(fn(this.value)):this;
    }
}
Maybe.of(null).map(x=>x.toString())

9.5 Either #

class Either{
    constructor(left, right) {
        this.left = left;
        this.right = right;
    }
    static of = function (left, right) {
        return new Either(left, right);
    };
    map(fn) {
        return this.right ?
            Either.of(this.left, fn(this.right)) :
            Either.of(fn(this.left), this.right);
    }
    get value(){
        return this.right||this.left;
    }
}
//处理默认值
let user = {gender:null};
let result = Either.of('男',user.gender)
.map(x=>x.toUpperCase())
console.log(result.value);

//处理异常
function parse(str){
    try{
        return Either.of(null,{data:JSON.parse(str)});
    }catch(error){
        return Either.of({error:error.message},null);
    }
}
console.log(parse("{}").value);
console.log(parse("{x}").value);

9.6 ap #

class Ap{
    constructor(value){
        this.value = value;
    }
    static of (value){
        return new Ap(value);
    }
    map(fn){
        return  new Ap(fn(this.value));
    }
    ap(functor) {
        return Ap.of(this.value(functor.value));
    }
}

const A = Ap.of(x=>x+1);
const B = Ap.of(1)
let result = A.ap(B);
console.log(result);

9.7 Monad 函子 #

函子嵌套

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

let result = Functor.of('a')
   .map(x=>Functor.of(x+1))
   .map(x=>Functor.of(x.value+2))
   .map(x=>Functor.of(x.value+3))
console.log(result.value.value);
let r1 = [1,2,3].map(item=>[item+1]);
console.log(r1);//[ [ 1, 1 ], [ 2, 2 ], [ 3, 3 ] ]
console.log(r1[0][0]);
let r2 = [1,2,3].flatMap(item=>[item+1]);
console.log(r2);
console.log(r2[0]);
class Monad {
    constructor(value) {
        this.value = value;
    }
    static of(value) {
        return new Monad(value);//a
    }
    map(fn) {
        return new Monad(fn(this.value));
    }
    join() {
        return this.value;
    }
    flatMap(fn){
        return this.map(fn).join();
    }
}

/* let result = Monad.of('a')
   .flatMap(x=>{
       let r = Monad.of(x+1);
       r.name = 'x1';
       return r;
   })//new Monad('a1')); 返回的Monad.value=Monad.of(x+1),后面用的是Monad.of(x+1)
console.log(result); */

let result = Monad.of('a')
   .flatMap(x=>Monad.of(x+1))
   .flatMap(x=>Monad.of(x+2))
   .flatMap(x=>Monad.of(x+3))
console.log(result.value);
//flatMap() 方法首先使用映射函数映射每个元素,然后将结果压缩成一个新数组
let r1 = Array.of(1,2,3).map(x=>x+1);
console.log(r1);
let r2 = Array.of(1,2,3).map(x=>[x+1]);
console.log(r2);
let r3 = Array.of(1,2,3).flatMap(x=>[x+1]);
console.log(r3);

9.8 IO函子与副作用 #

过程调用

const { compose } = require("lodash/fp");
let localStorage = {
    getItem(key){
        if(key === 'data')
            return `{"code":0,"userId":"1"}`;
        else if(key === "1"){
            return `{"id":1,"name":"zhangsan","age":18}`;
        }
    }
}
 function printUsers() {
    const response = localStorage.getItem('data');
    const data = JSON.parse(response);
    const users = data.userId;
    const user = localStorage.getItem(data.userId);
    console.log(user);//输出副作用
}
printUsers();

IO函子

const { compose } = require("lodash/fp");
let localStorage = {
    getItem(key){
        if(key === 'data')
            return `{"code":0,"userId":"1"}`;
        else if(key === "1"){
            return `{"id":1,"name":"zhangsan","age":18}`;
        }
    }
}
class IO {
    constructor(value) {
        this.value = value;
    }
    map(fn) {
        return new IO(compose(fn, this.value));
    }
    join() {
        return this.value();
    }
    start(callback) {
        callback(this.value());
    }
}
const readByKey = key => new IO(() => localStorage.getItem(key));
const parseJSON = string => JSON.parse(string);
const writeToConsole = console.log;
readByKey('data')
    .map(parseJSON)
    .start(writeToConsole);

链式调用

const { compose } = require("lodash/fp");
let localStorage = {
    getItem(key){
        if(key === 'data')
            return `{"code":0,"userId":"1"}`;
        else if(key === "1"){
            return `{"id":1,"name":"zhangsan","age":18}`;
        }
    }
}
class IO {
    constructor(value) {
        this.value = value;
    }
    map(fn) {
        return new IO(compose(fn, this.value));
    }
    flatMap(fn) {
        return new IO(compose(x=>x.value(), fn, this.value));
    }
    start(callback) {
        callback(this.value());
    }
}
const readByKey = key => new IO(() => localStorage.getItem(key));
const parseJSON = string =>  JSON.parse(string)
const writeToConsole = console.log;
let ret = readByKey('data')
    .map(parseJSON)
    .map(item=>item.userId)
    .flatMap(readByKey)
    .map(parseJSON)
    .start(writeToConsole);;

9.9 task函子 #

异步执行任务

const Task = execute => ({
    execute
});
function get(url) {
    return Promise.resolve({ "code": 0, "userId": "1" });
}
const request = url => Task((resolve,reject) => get(url).then(resolve,reject));
request('data')
    .execute(user => console.log(user),error => console.error(error));

实现map

const Task = execute => ({
    execute,
    map: fn => Task(resolve => execute(x => resolve(fn(x))))
});
function get(url) {
    return Promise.resolve({ "code": 0, "userId": "1" });
}
const request = url => Task((resolve,reject) => get(url).then(resolve,reject));
request('data')
    .map(x => x.userId)
    .execute(user => console.log(user),error => console.error(error));

实现 flatMap

const Task = execute => ({
    map: fn => Task(resolve => execute(x => resolve(fn(x)))),
    flatMap: fn => Task(resolve => execute(x => fn(x).execute(resolve))),
    execute
});
function get(url) {
    if (url === 'data')
        return Promise.resolve({ "code": 0, "userId": "1" });
    else if (url === "1") {
        return Promise.resolve({ "id": 1, "name": "zhangsan", "age": 18 });
    }
}
const request = url => Task(resolve => get(url).then(resolve));
request('data')
    .map(x => x.userId)
    .flatMap(request)
    .map(x => x.name)
    .execute(user => console.log(user));

10.实际应用 #

10.1 react #

10.2 vue #

<template>
  <div>
    <span>count is {{ count }}</span>
    <span>plusOne is {{ plusOne }}</span>
    <button @click="increment">count++</button>
  </div>
</template>

<script>
import { value, computed, watch, onMounted } from 'vue'
export default {
  setup() {
    // reactive state
    const count = value(0)
    // computed state
    const plusOne = computed(() => count.value + 1)
    // method
    const increment = () => { count.value++ }
    // watch
    watch(() => count.value * 2, val => {
      console.log(`count * 2 is ${val}`)
    })
    // lifecycle
    onMounted(() => {
      console.log(`mounted`)
    })
    // expose bindings on render context
    return {
      count,
      plusOne,
      increment
    }
  }
}
</script>