函数式编程

136 阅读7分钟

函数式的概念

函数式编程通过使用函数来将值转换成抽象单元,接着用于构建软件系统。

什么是软件系统

  • 展示数据 - 纸质化 -- 信息化 -- 展示数据 (统计,复杂计算,可视化图表...)
  • 模块化 - CMJ, ESM,-- 管理变量。

函数式编程和传统编程思路上的区别是什么?

  • 把一个数据,最终通过一些方法,一步一步变成另外一些数据。
  • 函数式编程,他更关注的是,“谓词”,需要有哪些函数?

函数式编程和命令式编程的区别

  • 命令式编程,命令式编程往往是建立在直线操作和检查程序状态之上,
  • 函数式编程更倾向于的是,函数式编程的思路是将程序拆分并抽象成多个函数再组装回去。

函数式编程和面向对象编程的区别

面向对象强调的是,一切皆是对象,函数式强调的是,一切皆是函数。 面向对象强调的是主语,函数式强调的是谓语。

我要做一道菜

命令式 编程:

从养一头猪开始,养大、喂养、斩杀、切肉、炸、炒、上菜。

OOP 面向对象 编程;

谁,来做,pork -- chief -- dish

函数式编程:

喂养的function;斩杀的function,制作的function;-- compose -> dish.


//写一个数组的处理函数,modifyArray
//input:[1,2,3,4,5,6] ->[2,4,6,8,10,12]



第一种:
const modifyArrCmd = (arr)=>{
const result=[];
for(let i =0 ;i<arr.length;i++){
if(arr[i]!==0){result.push(arr[i] * 2)}}
return result;
}
let arr = [0,1,2,3,4,5,6];
console.log(modifyArrCmd(arr));


第二种:面向对象 编程
//思路:
//第一步:数组要经历一个筛选函数,把0筛选掉
//第二步:数组要经理一个乘法函数,每一个数据,*2;

const modifyArrFP = (arr) => arr.filter(Boolean).map(item =>item*2);
console.log(modifyArrFP(arr));

** 4_compose.js

第三种:函数式编程
//把0筛选掉
const filterBoolean = arr => arr.filter(Boolean);
const filterBigger = num => arr => arr.filter(item => item <= num);
//过滤掉大于10的
const filterBigger10 = filterBigger(10)
//科里化,把一个乘法函数,拆解了
const mutiply = num => arr => arr.map(item=> item + num );
//每一个数据,*2
const mutiplyTwo = mutiply(2);
//每一个数据,*3
const mutiplyThree = mutiply(3);

const compose=(...rest)=>startNum=>rest.reduce((total,item)=>item(total),startNum)
//等价于
const compose=(...rest)=>startNum=>rest.reduce((total,item)=>{total=item(total)},startNum) 
//fn1, fn2,fn3 -> fn3(fn2(fn1(startNum)))
//把数据先 * 2 再过滤成1-10的数组
const modifyArr2 = compose(filterBoolean,mutiplyThree,filterBigger10);

const arr = [0,1,2,3,4,5,6];
console.log(modifyArr2(arr));

//对修改开放,对扩展开放。

//更想说的一点是, map/filter/reduce 其实比 forEach 更加的函数式
//React比Vue更加的函数式(FPify)

** 5_decorator.js(设计模式)

const fn =()=>{decorator.js
     console.log(`this is an 接口 `);
}
const bf1 = {}=>{
     console.log(`this is the before function running`);
}
//请实现一个 warppedFn 包装我的fn ,让bf1 先执行
const warppedBefore =  function(fn,before){
     return function(...args){
       before();
       returnfn.apply(this,args);
     }
}

const warppedFn  = warppedBefore(fn,bf1);
warpperFn()

//1.before 能不能是一个数组。
//2,怎么样执行
//3.字符串的校验
const compose=(...rest)=>startNum=>rest.reduce((total,item)=>item(total),startNum)

const warppedBefore =  function(fn,...bf){
     return function(args){
       compose(...bf)(args);//接口请求的参数进行字符串校验
       return fn.apply(this,c);
     }
}
const warppedFn  = warppedBefore(fn,bf1,bf2,bf3);
warpperFn([0,1,2,3,4,5]);

** 6_strategy.js(策略模式)
计算一个折扣
var strategies = {
"S":salary => salary * 4;
"A":salary => salary * 3,
"B":salary => salary * 2
                 }
var calculateBonus =  function(level,salary){
       return strategies[level](salary);
}


函数式的特点

函数是第一等公民

高阶函数

  • 函数可以作为返回值
    • **就给函数一些组合,缓存的能力 **。
  • 函数可以作为参数;
    • **就给函数提供了一个包装的能力 **。

围绕着函数,取代面向过程式的代码,往往能够有以下收益:

  • 表达力更加清晰,因为“一切都是函数”,通过函数的合理命名,函数原子的拆分,我们能够一眼看出程序在做什么,以及做的过程;
  • 利于复用,因为“一切都是函数“,通过函数的合理命名,函数原子的拆分,我们能够一眼看出来程序在做什么,以及做的过程;
  • 利于维护,纯函数和幂等性保证同样的输入就有同样的输出,在维护或者调试代码时,能够更加专注,减少因为共享带来的潜在问题。

纯函数

一个函数如果输入参数确定,输出价格是唯一确定的,那么它就是纯函数。

  • 无状态
  • 无副作用 - sideEffect
//副作用很大
const minusCount = ()=>{
window.count--;
}
//没那么纯,但是,没有副作用
const minusCount =(global)=>{
global.count--;
}

//tree shaking 可以做性能优化。
methods:{
//一个接口
getDataList(){
get({query:this.queryalue}).then()res=>{this.resDate=res.data;})
}
}
const setHtml = (node.html) =>{
node.innerHtml = html;
}
//array 的 splice 方法(会修改原函数)
//array 的 slice 方法,更纯净一些。
  • 无关时序
  • 幂等(无论调用多少次,结果是相同的。)

多人协作的开发过程中,如何去考虑副作用?

函数的编写

  • 在我们编写一个函数时,比如,一个典型的接口请求,我们怎么写?

组件的封装

在我们进行一个组件的封装时,我们一个考虑哪些问题和维度?

  • 副作用
    • css 隔离 。css 类重名了;
  • 定位
    • 纯UI的组件吗 -- 把搜索,下拉框的信息,放在外面。
  • 复用性
    • props 越多,复用性越强,组件越难维护。 把一个组件,拷贝过来,会又什么问题?

架构的设计

  • 反过来,我们思考一下,模块化,微前端,究竟是解决什么问题?副作用究竟是什么?

熵增

  • 熵 -- 代表这混乱程度。在理解了副作用的基础上,大家可以往这个角度进行延伸思考。

我对闭包的定义

  • 函数执行的上下文,不是本身的词法作用域。

函数的科里化

在计算机科学中,柯里话(currying),有译为卡瑞话或加里话,是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数而已返回结果的新函数的技术,这个技术由克里斯托弗.斯特雷奇以逻辑学家哈斯凯尔.加里命名。 抽象 -- 适用 -- 越多,tradeoff。 具体 -- 不适用 --越少。

add(1)(2) == 3;
add(1)(2)(3) == 6;
1.函数的柯里话。
2.当我去使用这个函数的时候,我能够转成value。

const add = arg1 =>{
    let args = [arg1];
    const fn = arg2 =>{
         args.push(arg2)
      return fn;
  }
//当我进行判断的时候,我再调用这个方法
//重写toString方法,当进行==判断的时候会调用toString(隐式转换)

fn.toString = function(){
        return args.reduce((prev,item)=>prev+item,0)
        }
    return fn;
   }
console.log(add(1)(2) == 3,add(1)(2)(3)(4) == 10)

const add2 =(...arg1)=>{
    let args = [...arg1];
    const fn=(...arg2)=>{
       args = [...args,...arg2];
       if(args.length ===5){
           return args.reduce((prev,item) => prev+item,0)
       }else{
           return fn;
       }
    }
    return fn;
 }

console.log(add2([1,2,3](4)(5))==15)
 //如果是要输出6呢?
 //add(1)(2)(3)
 //new Promise()
 //lazyMan
 
 
 add(1)(2)(3)..toString()
 add(1)(2)(3) .toString()

前端面试中的LazyMan

实现一个LazyMan,可以按照以下方式调用:\
LazyMan(“Hank”)输出:\
Hi! This is Hank!
LazyMan(“Hank”).sleep(10).eat(“dinner”)输出\
Hi! This is Hank!\
//等待10秒..\
Wake up after 10\
Eat dinner~
LazyMan(“Hank”).sleepFirst(5).eat(“supper”)输出\
//等待5秒\
Wake up after 5\
Hi This is Hank!\
\
Eat supper\
以此类推
  • 题目解析:看起来是链式调用并且有流程控制其中sleepFisrt要在所有函数之前执行。大致的思路是创建一个任务队列,将每一项任务(输出名字、吃饭、睡觉)都放进队列里按顺序执行。代码如下:
let LazyMan = function(name){


let _lazyMan = {
  sayHi : ()=>{
      queue.push(()=>{
       console.log(`Hi! This is`+name)
       this.next();
       })
   },
  eat :(food)=>{
      queue.push(()=>{
          console.log(`Eat`+food)
          this.next();
      })
   },
  sleep:(second)=>{
         queue.push(()=>{
             setTimeout(()=>{
       console.log(`wake up after` + second + '秒')
             },second * 1000)
          })
      }
   },
   sleepFirst:function(second){
      queue.unshift(()=>{
            setTimeout(()=>{
      console.log(`wake up before` + second +'秒')
            },second * 1000)
        })
      }
     },
    next:function(){
      let fn = queue.shift()
      fn && fn()
     }
  },
  
 let queue = [],//存放函数的任务队列
 _lazyMan.sayHi();//将打招呼放在队列
 setTimeout(function(){//开始执行任务队列里的函数
   _lazyMan.next();
        })
  return _lazyMan; 
}

函数式的应用与实战

函数的缓存与闭包

//假如,我有一个计算密集型的函数
//我希望对这个函数进行包装,如何让这个函数如果之前计算过了
//我就不再计算了,直接返回值。


const menorize = fn =>{ 
   let cacheMap = {}; 
   return function(...args){ 
       console.log(args,"args");//[1,2]
       const cacheKey = args.join(`-`);
       if(cacheMap in cacheMap){ 
             return cacheMap[chcheKey];
       }else{
             return cacheMap[cacheKey] = fn.apply(this||{},args);
       }
    }
}

function add(a,b){ 
    console.log(a,b);//1,2
    return a+ b;
} ;

let fun = menorize(add) ;
console.log(fun(1,2),"4444")//3