Skip to content

函数式编程

  • 前置知识
    • js 基础
    • js面向对象

函数式编程含义

  • 函数式编程是一种强调以函数使用为主的软件开发风格 ,也是一种范式。

  • 某些函数式编程语言Haskell、lisp、Scheme等。

js中函数式编程

  • 数学中函数

    f(x) = y;

  • js中的函数

    js
       let factor = 3;
       let totalNum = num=>factor*num;
       console.log( totalNum(3) );
  • 对比两种函数,基于数学函数 来修复 js函数

    js
       let totalNum = (num,factor)=>factor*num;
       console.log( totalNum(3,3) );
  • js是多范式编程语言,但是函数作为一等公民,函数式编程具有天然优势。

函数式编程中涉及到的概念

纯函数

  • 函数式编程基于纯函数

    • 纯函数是对给定的输入返还相同输出的函数;例如

      js
      let double = value=>value*2;
  • 纯函数意义

    • 纯函数可以产生可测试的代码

      • 不依赖外部环境计算,不会产生副作用,提高函数的复用性。

        js
        test('double(2) 等于 4', () => {
          expect(double(2)).toBe(4);
        })
    • 可读性更强 ,js函数不管是否是纯函数 都会有一个语义化的名称,更便于阅读。

    • 可以组装成复杂任务的可能性。符合模块化概念及单一职责原则。

作业:通过声明式编程方式实现 filter 及 map 达到原生的效果

html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    
</body>
<script>
   Array.prototype.myMap = function(fn){
        let resArr = [];
        for(let i=0;i<this.length;i++){
            resArr.push(fn(this[i]));
        }
        return resArr;
   }
   let users = [{
       name:"张三",
       age:20
   },{
       name:"李四",
       age:21
   },{
       name:"王五",
       age:28
   }]
//   let res =  users.myMap(item=>item);
//   console.log(res);
Array.prototype.myFilter = function(fn){
    let resArr = [];
    for(let i=0;i<this.length;i++){
        if(fn && fn(this[i])){
            resArr.push(this[i])
        }
    }
    return resArr;
}
let res =  users.myFilter(item=>item.age>20);
console.log(res);

</script>
</html>

高阶函数

  • 高阶函数定义

    • 高阶函数:以函数作为输入或者输出的函数被称为高阶函数(Higher-Order Function)。
  • 高阶函数的抽象

    • 一般高阶函数用于抽象通用问题,简而言之,高阶函数就是定义抽象。

      • 命令式循环(注重“如何”做,注重过程);

        js
        let arr = [1,2,3];
        for(let i=0;i<arr.length;i++){
            console.log(arr[i]);
        }
      • 通过高阶函数抽象过程,声明式编程(注重做“什么”,注重结果);

        js
        const forEach = function(arr,fn){
            for(let i=0;i<arr.length;i++){
                fn(arr[i]);
            }
        }
        let arr = [1,2,3];
        forEach(arr,(item)=>{
            console.log(item);
        })

        上面通过高阶函数 “forEach”来抽象循环"如何"做的逻辑,直接关注 做"什么"

  • 高阶函数的缓存特性

    • 主要是利用函数的闭包

      • once 高阶函数
      js
      const once = (fn)=>{
          let done = false;
          return function(){
              if(!done){
                  fn.apply(this,fn);
              }else{
                  console.log("this fn is already execute");
              }
              done = true;
          }
      }
      
      function test(){
          console.log("test...");
      }
      let myfn =  once(test);
      myfn();
      myfn();

函数柯里化

  • 什么是柯里化?

    • 柯里化是把一个多参数函数转化成一个嵌套的一元函数的过程;

      • 如下二元函数

        js
        let fn = (x,y)=>x+y;
      • 柯里化函数

        js
        const curry = function(fn){
            return function(x){
                return function(y){
                    return fn(x,y);
                }
            }
        }
        let myfn = curry(fn);
        console.log( myfn(1)(2) );
  • 多参数函数柯里化

    js
    // 多参数柯里化;
        const curry = function(fn){
            return function curriedFn(...args){
                if(args.length<fn.length){
                    return function(){
                        return curriedFn(...args.concat([...arguments]));
                    }
                }
               return fn(...args);
            }
        }
        const fn = (x,y,z,a)=>x+y+z+a;
        const myfn = curry(fn);
        // console.log(myfn(1)(2));
        console.log(myfn(1)(2)(3)(1));
  • 柯里化意义

    • 让纯函数更”纯“,每次接受一个参数,松散解耦
    • 某些语言及特定环境下只能接受一个参数
    • 惰性执行

组合(composition)和管道(pipe)

组合(composition)

  • 组合函数:无需创建新的函数,通过基础函数解决眼前问题。

    • 组合高阶函数 map 及 filter;

      • map高阶函数

      • 组合map及filter来使用

    • compose组合

      可以封装组合函数来实现函数执行

      • 2个函数组合
      js
      function afn(a){
          return a*2;
      }
      function bfn(b){
          return b*3;
      }
      const compose = (a,b)=>c=>a(b(c));
      let myfn =  compose(afn,bfn);
      console.log( myfn(2));
      • 多函数组合

        js
        const compose = (...fns)=>val=>fns.reverse().reduce((acc,fn)=>fn(acc),val);

管道(pipe)

compose 执行是从右到左,pipe是从左至右的执行。函数如下:

js
const pipe = (...fns)=>val=>fns.reduce((acc,fn)=>fn(acc),val);

​ 管道、组合 取舍 :管道及组合最大区别在于执行顺序的不同,数据流向不同,达到目的是类似的。所以无优 劣之分,保持团队风格统一就好了。

组合及管道的意义 把很多小函数组合起来完成更复杂的逻辑。

Pointfree 编程风格

概念:不适用处理的值,只合成运算过程,也就是无值风格。

  • 获取所有的句号

    js
    const sentenceNum = str=>str.match(/。/g);
    let str = "大家好,我是中国人。我爱中国。我们同住地球村。";
    console.log(sentenceNum(str));
  • 统计长度

    js
    const countFn = arr=>arr.length;
  • 判断奇偶

    js
    const oddOrEven = num=>num%2===0?"偶数":"奇数";
  • pointfree风格组合函数使用:找到句号统计长度最后判断奇偶数

    js
    let str = "大家好,我是中国人。我爱中国。我们同住地球村。";
    const myfn = compose(oddOrEven,countFn,sentenceNum);
    console.log(myfn(str));

js函数式编程库

  • lodash.js 、ramda.js 、Underscore.js

  • 通过 ramda.js实现数据筛选功能;

函数式编程在redux 中应用

  • Redux 整体是通过函数式 编程思维实现的;

  • createStore 简单实现

    js
    const createStore = (reducer, initialState) => {
        const store = {};
        let  currentState = initialState;
        const  listeners = [];
        const getState = () => currentState;
        const subscribe = (listener) => {
            listeners.push(listener);
        };
        const dispatch = (action) => {
            // store.state = deepFreeze(reducer(store.state, action) );
            currentState = reducer(currentState, action);
            listeners.forEach(listener => listener());
        };
        return {
            getState,
            subscribe,
            dispatch
        };
    };
    
    export {createStore};
  • 状态管理调用

    js
    import {createStore} from "../MyRedux.js";
    // import { createStore } from 'redux';
    function Counter(state={count:0},action) {
        switch (action.type) {
            case "ADD_COUNT":
                return {count:state.count + 1}
                break;
            case "REDUCE_COUNT":
                return {count:state.count - 1}
                break;
            default:
                return state;
                break;
        }
    }
    var store = createStore(Counter);
    
    
    btn.onclick = function(){
        // console.log(store);
        store.dispatch({type:"ADD_COUNT"});
    }
    
    store.subscribe(() => {
        // console.log(store)
        renderDom()
    })
    
    function renderDom(){
      let mydiv =   document.querySelector(".countDiv");
      if(typeof store.getState() !== "undefined"){
        mydiv.innerHTML = store.getState().count;
      }
    }

ddd

html
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>

</body>
<script>
    // 编程范式  : 面向过程  面向对象  
    // 是一种强调以函数使用为主的软件开发风格 ,也是一种范式。
    // 多范式语言 函数一等公民
    // 一、纯函数:纯函数是对给定的输入返还相同输出的函数.
    // f(x) = y;  1->2  2->4 3->6...


    // 不是纯函数
    // let factor = 3;
    // const y = function (x){
    //     return x * factor ;
    // }
    // console.log(y(2));
    // const y = function (x){
    //     let factor = 2;
    //     return x * factor ;
    // }

    // 意义:复用性  可推测性 组合成复杂功能

    // test("double(2) 等于 4" ,()=>{
    //     expect(double(2)).toBe(4);
    // })


    // 高阶函数:以函数作为输入或者输出的函数被称为高阶函数(Higher-Order Function)

    // function test(cb){
    //     cb && cb();
    // }
    // test(function(){
    //     console.log("test");
    // })

    // function test(){
    //     return function(){
    //         console.log("test");
    //     }
    // }
    // test()();

    // 意义;抽象
    // 命令式 强调“如何做”    声明式编程 : “做什么”
    // let arr = [1,2,3];
    // for(let i=0;i<arr.length;i++){
    //     console.log(arr[i]);
    // }

    // 声明式
    // const forEach = function(arr,fn){
    //     for(let i=0;i<arr.length;i++){
    //       fn && fn(arr[i]);
    //     }
    // }

    // forEach(arr,function(item){
    //     console.log(item);
    // });

    // let arr = [true,false,true];

    // const every = function(arr,fn){
    //     let result = true;
    //     for(let i=0;i<arr.length;i++){
    //         result = result && fn(arr[i]);  
    //     }
    //     return result;
    // }

    // let res =  every(arr,item=>item);
    // console.log(res);

    // 缓存特性
    // const once  = function(fn){
    //     let done = false;
    //     return function(){
    //         if(!done){
    //             fn();
    //             done = true;
    //         }else{
    //             console.log("已经执行过了");
    //         }
    //     }
    // }

    // function test(){
    //     console.log("test函数");
    // }
    // let myFn = once(test);

    // myFn();
    // myFn();

    // function memorize(fn){
    //     let cache = [];
    //     return function(){
    //         cache.push(fn(...arguments));
    //         return cache;
    //     }
    // }

    // const sum = function(a,b){
    //     return a + b;
    // }

    // let mySum = memorize(sum);
    // console.log( mySum(1,2));
    // console.log( mySum(2,2));
    // console.log( mySum(3,2));

    // 柯里化  curry  : 把一个多参数函数转化成一个嵌套的一元函数的过程

    // function add(x,y,z,o){
    //     return x+y+z+o;
    // }
    // add(1,2,3);
    // add(1)(2)(3); 

    //柯里化
    // const curry = function(fn){
    //     return function(x){
    //         return function(y){
    //             return function(z){
    //                 return fn(x,y,z);
    //             }
    //         }
    //     }
    // }
    // 通用柯里化
    // x ---> x,y --->x,y,z ....
    // const curry = function (fn) {
    //     return function curryFn(...args) {
    //         if (args.length < fn.length) {
    //             return function () {
    //                 return curryFn(...args, ...arguments);
    //             }
    //         } else {
    //             return fn(...args);
    //         }
    //     }
    // }



    // let myAdd = curry(add);
    // console.log(myAdd(1)(2)(3)(4));

    // 意义;
    // 参数复用
    // let str1 = "abcfdsafd";
    // let str2 = "fdsaljllj";

    // function addPre(pre,str){
    //     return pre + "-" + str;
    // }
    // let pre = "AA";
    // // console.log(addPre(pre,str1));
    // // console.log(addPre(pre,str2));

    // // 参数复用
    // let addPreFn = curry(addPre)(pre);
    // console.log(addPreFn(str1));
    // console.log(addPreFn(str2));

    // 延迟执行;
    // function getAjax(url,method){
    //     let xhr;
    //     if(XMLHttpRequest){
    //         xhr = new XMLHttpRequest();
    //     }else{
    //         xhr = new ActiveXObject(); 
    //     }
    //     return xhr;
    // }


    // function getAjax(url,method){
    //     if(XMLHttpRequest){
    //         return function(){
    //             return new XMLHttpRequest();
    //         }
    //     }else{
    //         return function(){
    //             return new ActiveXObject(); 
    //         }
    //     }
    // }

    // let myGetAjax = curry(getAjax)("api/users");
    // let xhr =  myGetAjax("get");
    // console.log(xhr());

    // bind(this)(1)(2)....



    // 组合及管道  compose  pipe

    // function aFn(num) {
    //     return num + 2;
    // }

    // function bFn(num) {
    //     return 2 * num;
    // }

// 基于值来计算
// console.log( bFn(aFn(5)));
// 无值编程 (合成运算过程);
// pointFree风格:无值编程  合成运算过程  。

// 组合:从右至左执行的;
// 管道:从左至右执行的;
// const compose = function(bFn,aFn){
//     return function(num){
//         return bFn(aFn(num));
//     }
// }

// const compose = function(...fns){
//     return function(arg){
//        return  fns.reverse().reduce((acc,fn)=>{
//            return fn(acc);
//        },arg)
//     }
// }

// 组合
// const compose = (...fns)=>arg=>fns.reverse().reduce((acc,fn)=>fn(acc),arg);

// 管道
// const pipe = (...fns)=>arg=>fns.reduce((acc,fn)=>fn(acc),arg);

// let myFn = compose(bFn,aFn);
// let res =  myFn(5);
// console.log(res);


// const str = "大家好,我是中国人。我爱中国。我们同住地球村。";

// // function formatStr(str){
// //     let res = str.match(/。/g);
// //     return res.length%2===0?'偶数':'奇数';
// // }
// // 获取句号
// const getPeriod = str=>str.match(/。/g);
// // 获取长度
// const getLength = str=>str.length;
// // 判断奇偶
// const oddOrEven = num=>num%2===0?'偶数':'奇数';

// const formatStr = compose(oddOrEven,getLength,getPeriod);


// console.log(formatStr(str));


// forEach map  filter some every reduce ....

// 作业:实现 myMap  和 myFilter 


// let arr = [{
//     name:"张三",
//     age:20
// },{
//     name:"李四",
//     age:25
// },{
//     name:"王五",
//     age:28
// }]

// let res =  arr.map(item=>{
//   console.log(item);
//   return item;
// });
// console.log(res);
// let res = arr.filter(item=>item.age>25);
// console.log(res);

</script>

</html>

redux的使用

html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <div class="numDiv"></div>
    <button>点击</button>
</body>
<script type="module">
import createStore from './createStore.js';


function Counter(state={num:0},action){
    switch(action.type){
        case 'ADD':
            return {num:state.num+2};
            break;
        case 'MINUS':
            return {num:state.num-1}
            break;
        default:
            return state;
            break;
    }
}

let store = createStore(Counter);
console.log(store);
let btn = document.querySelector("button");
btn.onclick = function(){
    store.dispatch({type:'ADD'})
}

store.subscribe(()=>{
    renderDom();
})

function renderDom(){
    let mydiv = document.querySelector(".numDiv");
   let state =  store.getState();
   mydiv.innerHTML = state.num;
}

</script>
</html>

函子

容器函子Functor

函子是函数式编程里面最重要的数据类型,也是基本的运算单位和功能单位。

  • 定义自己的Functor。

    js
    class Container{
        constructor(value){
            this._value = value;
        }
        static of(val){
            return new Container(val);
        }
    }
    let res = Container.of(1);
    console.log(res);
  • 通过map修改Functor值

js
class Container{
    constructor(value){
        this._value = value;
    }
    static of(val){
        return new Container(val);
    }
    map(fn){
        return Container.of(fn(this._value));
    }
}
let res = Container.of(1);
// console.log(res);
let reslut =  res.map(item=>{
    // console.log(item);
   return item+3
}).map(item=>{
    return "最终结果是" + item;
})

MeBe函子

处理空值的函子

js
class Mabe{
    constructor(val){
        this._value = val;
    }
    static of(val){
        return new Mabe(val);
    }
    isNoThing(){
        return (this._value===null || this._value=== undefined);
    }
    map(fn){
        return this.isNoThing()?Mabe.of(null):Mabe.of(fn(this._value));
    }
}

Either 函子

js
class Either extends Functor{
    constructor(left,rigth){
        super();
        this.left = left;
        this.right = rigth;
    }
    map(f){
        return this.right?Either.of(this.left, f(this.right)) :
      Either.of(f(this.left), this.right);
    }
    static of(left, right){
        return new Either(left, right);
    }
}

Released under the MIT License.