[路飞]_企图讲透js函数式编程(上)

function selectGirlFriend(18岁少女,38岁富婆){
    return [38岁富婆,18岁少女];
}
复制代码

以上是一个纯纯类c风格函数,众所周知c语言是典型的面向过程范式的编程语言,js作为多范式编程语言,函数式或许是其最重要的一部分(我感觉 或许可以去掉,但是作为初学者还是不这么武断)。 下面步入正题,企图给大家说明白函数式编程的点点滴滴。

高阶函数

什么是高阶函数? 不急,我先告诉你什么是一阶函数,开头选择女朋友那串函数就是一个一阶函数。这里其实可以通过数学领域的函数来进行理解,返回值相当于 y=f(x) 的y,而函数的参数相当于其中的x,是一个x=>y的映射关系。高阶函数是什么?是在一阶函数的基础上,如果参数是一个函数,亦或者是返回值是一个函数,那他都可以叫做高阶函数。

高阶函数-参数是函数

这个大家肯定接触的很多了,例如数组方法中的map,filter,some,every... 这里给大家手写一个map加深一下理解

    //赤裸裸的参数是函数
    let map=(array,fn)=>{
    let result=[]//存放数组原元素处理后的元素
        for(item of array){
        result.push(fn(item));
        }
        return result;
    }
复制代码

其他的数组方法大同小异,都是参数是函数的高阶函数

高阶函数-返回值是函数

//once
function once(fn){
  let done=false;
  return function(){
    if(!done){
      done=true;
      //返回值是函数
      return fn.apply(this,argument)
    }
  }
}
let pay=once((money)=>{console.log(money)};
pay(5);
pay(5);
复制代码

once函数的返回值和参数都是函数,也属于高阶函数。但是这里,请大家看看这个函数在干什么? 这个函数的功能是 让某一函数只许执行一次。大家想想找个逻辑该怎么实现,是不是得用个变量来标记函数是否有没有被执行过?这个变量只能放到函数内呀,学过c语言的都知道,函数是在栈中运行,一但运行结束,函数中所有的东西都会烟消云散,也就是说这个标记变量显然会没了,显然once函数的功能是实现不了的,而现实这段代码实现运行一次的逻辑是ok的(不信你跑跑,测试用例都给大家写了),这是不是一个细思恐极的灵异事件?
当然这肯定不是灵异事件,关键就在于这一段代码:

let pay=once((money)=>{console.log(money)}; 这段代码让标记变量done没有被杀掉。这个知识点就是闭包,下面给大家详细介绍。

闭包

闭包:函数和其周围的状态(词法环境)的引用捆绑在一起形成闭包。

可以在另一个作用域中调用一个函数的内部函数并访问到该函数作用域中的成员

人话:引用一个函数,函数里的成员会得到保留,不会立刻释放 上面pay引用了once函数,函数中的done会得到保留,就是这么神奇。 闭包的本质:函数在执行的时候会放到一个执行栈上,执行完成后,会被执行栈移除,但是堆上的作用域成员因为被外部引用不能释放,因此内部函数依然可以访问外部函数的成员。

上面纯概念或许有些抽象,给大家点案例。 案例一:

//将Math.pow(number,pow)细分为求二次方,三次方,降低参数冗余度
function makePower(power){
  return function(number){
    return Math.pow(number,power)
  }
}
//生成求二次方的power
power2引用了makePower 且给了参数2,makepower会卡在这里,不会立刻释放
let power2=makePower(2);
//生成求三次饭的power
let power3=makePower(3);

cosole.log(power2(4));
复制代码

案例二:

//求工资 基本工资+绩效工资
function makeSalary(base){
  return function(performance){
    return base+performance;
  }
}
//同上 level1是makeSalary点引用
let level1=makeSalary(1000);
let level2=makeSalary(2000);
console.log(level1(1000));
console.log(level2(800));
复制代码

高阶函数的意义

抽象可以帮助我们屏蔽细节

高阶函数是用来抽象通用的问题

代码可以更简洁

纯函数

纯函数:相同的输入永远会得到相同的输出,而且没有任何可观察副作用,就是数学中函数的映射关系。 副作用:函数依赖于外部状态会让函数变得不纯

副作用来源:

  • 配置文件
  • 数据库
  • 获取用户的输入

副作用不可能完全禁止,要尽可能控制

概念确实晦涩,直接上案例,教会大家怎么判断纯函数。 先复习一下数组的两个方法:

slice 返回数组中的指定部分 不会改变原数组

splice 对数组进行操作返回该数组,会改变原数组。 大家可以发现slice无论运行多少次,结果都是一样的,而splice却不是,因为slice不会改变原array,splice会改变原array。splice就违反了纯函数最重要的:相同的输入需要得到相同的输出。

let array=[1,2,3,4,5];
//slice方法是一个纯函数
console.log(array.slice(0,3));
console.log(array.slice(0,3));
console.log(array.slice(0,3));
//splice方法不是一个纯函数
console.log(array.splice(0,3));
console.log(array.splice(0,3));
console.log(array.splice(0,3));

复制代码

lodash

给大家介绍一个库

官方介绍:现代化的使用的js库,提供了模块化、高性能以及一些附加的功能

之前很多人把Lodash当成一个功能库来使用,当es5、es6给原生js增加了很多方便的方法以后,就有很多人来讨论,是否还有使用Lodash的必要,其实Lodash提供了常用的便捷方法以外,还提供了跟函数式相关的便捷方法,比如说函数的柯里化、函数组合等

使用

  • npm init -y 初始化一个package.json文件
  • npm i lodash 下载lodash
  • 使用require引入lodash:const lodash = require('lodash')

纯函数的好处

1.可缓存 lodash中有memoize方法,可对纯函数进行缓存。 什么是纯函数缓存?因为纯函数的特性,同样的输入他的输出肯定是一样的,函数参数相同的情况下,多次调用函数,函数如果每次都从头到尾重新执行是很没必要的,多次调用函数时如果有个对象(key为参数,value为返回值)记录了调用的结果,直接返回 对象[key]就可以了,会显著的提高运行效率。 lodash提供的memoize 案例如下

const lodash=require('lodash')
function getArea(r){
  //console.log("r");
  return Math.PI*r*r;
}
let getAreaWithMemory=lodash.memoize(getArea);
console.log(getAreaWithMemory(8));
复制代码

memoize方法也不复杂,咱也手写一下

//cache缓存 参数=>返回值的键值对
let memoize=function(f){
  let cache=[];
  //console.log(cache);
  return function(){
    let key=JSON.stringify(arguments);
    //已经缓存直接返回缓存的结果,没缓存重新执行一次需要调用的函数
    cache[key]= cache[key] || f.apply(f,arguments);
    return cache[key];
  }
}
复制代码

其余的好处:

可测试:纯函数会使测试变得很容易

并行处理:并行处理可以随便运行纯函数,不会造成函数并发执行带来的一系列问题,因为纯函数与全局资源一点关系都没有。

猜你喜欢

转载自juejin.im/post/7030845704201306142