函数式编程

213 阅读4分钟

describe what to do, rather than how to do it。

这句话是函数式编程的思想精髓。它只关心输入数据和输出数据的关系。关注的是做什么而不是怎么做。

特点

惰性求值

表达式不在它被绑定到变量之后就立即求值,而是在该值被取用的时候求值。

确定性

函数无论在什么场景下,都会得到同样的结果。

缺点

数据复制比较严重

常用技术

函数式编程和过程式编程的思维方式完全不一样。过程式编程是在把具体的流程描述出来,所以可以不假思索,而函数式编程的抽象度更大,在实现方式上,有函数套函数、函数返回函数、函数里定义函数...常用的技术有:

递归(recursing)

通过反复调用自身,把一个复杂的问题用很简单的代码描述出来。

这里要注意:递归函数相当于一种循环调用,需要避免死循环,给定一个条件停止调用。

递归常用在嵌套数组或树的遍历中。例如深拷贝:

 function clone(o){
        var temp = {};
        for(var key in o){
            if(typeof o[key] == 'object'){
                temp[key] = clone(o[key]);
            }else{
                temp[key] = o[key];
            }
        }
        return temp;
    }

尾递归(tail recursion optimization)

当一个函数执行时的最后一个步骤是调用自身,这就叫做尾递归。

  • 优点:常规的函数递归很深的话,stack 受不了,并会导致性能大幅度下降。而尾递归优化技术再每次递归时都会重用 stack,这样能够提升性能。

  • 缺点:需要把函数内部所有用到的中间变量改写为函数的参数,语义不明显。

柯里化(currying)

就是把一个参数的函数,转化为参数函数, 然后将函数多层封装起来,每层函数都返回一个函数去接收下一个参数。

// 方法一:柯里化之前
function add(x, y) {
return x + y;
}

add(1, 2) // 3

// 方法二:柯里化之后

function addCurry(y) {
return function (x) {
  return x + y;
};
}
// let addCurry = (y) => (x) => x + y es6写法

let add2 = addCurry(2)
let result = add2(1) //result:3

有人可能会说,求2个数的加法,使用方法一很好啊,一目了然。确实,如果仅是简单的业务需求去求解2个数的加法,犯不着用上函数柯里化。这里多此一举 进行currying,主要是体现函数式编程的理念:

1. 把函数当成变量来用,关注描述问题而不是怎么实现,这样可以让代码更易读。
2. 因为函数返回里面的这个函数,所以函数关注的是表达式,关注的是描述这个问题,而不是怎么实现这个事情。

纯函数(pure function)

不依赖于程序执行期间函数外部任何状态或数据的变化,必须只依赖于其输入参数的函数。

// 非纯函数,有状态
var cnt = 0;
function increment(){
    cnt++;
}

//纯函数,无状态
function increment(cnt){
    return cnt+1;
}

诸如 ReactJS 和 Redux 等优质的库都使用了纯函数。使用纯函数的优点:

  • 可以直接测试。 如果传入相同的参数,它们将始终产生相同的结果。

  • 维护和重构代码变得更加容易。因为没有副作用,代码之间耦合少,因此改写会更容易。

高阶函数(higher order function)

高阶函数就是函数当参数,把传入的函数做一个封装,然后返回这个封装函数

map && reduce && filter

map

js中的map函数式array数组原型上的方法,返回一个新的数组。其定义为

/**
第1个参数是函数,数组中的每个元素都会执行这个函数;需要 return
第2个参数用作 "this" 的值,如果缺省,回调函数的 this 为全局对象
*/
array.map(function(currentValue,index,arr), thisValue)

//自定义map函数
Array.prototype.myMap = (callback, context) => {
context = context || window;
let newArr = [];
for(let i=0,len=this.length;i<len;i++) {
	if (typeof callback === 'function') {
      let val = callback.call(context,this[i],i,this);
      newArr[newArr.length] = val;
}
return newArr;
}

由此可见,map函数是一个高阶函数,一个纯函数。

filter

用于对数组进行过滤,返回一个新的数组.新数组中的元素是通过检查指定数组中符合条件的所有元素。定义为:

Array.filter(function(currentValue, index, arr), thisValue)
reduce

该方法对累加器和数组中的每个元素(从左到右)应用一个函数,最终合并为一个值。

array.reduce(function(total, currentValue, currentIndex, arr), initialValue)

参考文章

  1. 函数式编程入门-阮一峰
  2. 函数式编程范式-左耳听风
  3. 尾调用和尾递归
  4. 什么是纯函数_以及为什么要用纯函数?