什么是函数式编程
函数式编程是一种编程范型,Javascript就支持这种编程范型。
函数式编程的特点
函数是一等公民
在 Javascript 中,我们可以像对其他类型一样对待函数,我们将函数存在数组里、将函数作为参数传递、作为返回值返回…没有什么特别的地方
为什么我们强调函数是一等公民呢,有这样一个例子
1 2 3 4 5 6 7 8 9
| var getServerStuff = function(callback){ return ajaxCall(function(json){ return callback(json); }); }; var getServerStuff = ajaxCall;
|
在第一种写法中,给 ajaxCall 逃了一层是没有必要的,造成了代码冗余;还有一种情况是,当我们需要给 ajaxCall 添加一个参数的时候,包裹它的 getServerStuff 也需要跟着增加一个参数,这又使代码变得不利于维护。
第二种写法则一目了然,也不会出现添加一个参数需要修改多处的情况。相比起来,我们更认同第二种函数作为一等公民的写法。
纯函数的方式编程
先来看看什么样的函数被认为是纯函数呢,slice 和 splice 是我们经常会用到的两个 Javascript 中的方法
1 2 3 4 5 6 7 8
| let arr = [1, 2, 3, 4, 5]; let newArr = arr.slice(0, 3); console.log(arr); console.log(newArr); let newArr1 = arr.splice(0, 3); console.log(arr); console.log(newArr1);
|
阅读这段代码,我们发现二者的效果是一样的,但是 slice 并没有改变原来的数组,而 splice 则永久的改变了原数组,如果我们的本意只是截取数组的一段,那么我们认为 splice 是不纯的,因为它做了多余的事情(改变原数组)。
再看这样一个例子
1 2 3 4 5 6 7 8 9 10 11 12 13
| var minimum = 21; var checkAge = function(age) { return age >= minimum; }; var checkAge = function(age) { var minimum = 21; return age >= minimum; };
|
第一种写法中的 checkAge 函数返回的结果依赖于外部的 minimum,这可能造成对于相同的 age 输入, 返回的结果可能不同,因为 minimum 是一个不确定的因素,第二种纯函数的写法就能避免这种困扰,checkAge 函数的返回值只依赖 age, 也就是说,只要传入值 age 是相同的,返回值也一定是相同的,这就是纯函数。
如果参数是一个对象,我们可以考虑把它转化成一个不可变的(Immutable)对象,也有Immutable.js的库来帮助我们做这件事情。
纯函数的概念也整对应着数学中的函数概念,在这种映射关系中,每个 x 中的元素只对应一个 y 中的元素

引用透明性(referential transparency): 如果一段代码可以替换成它执行所得的结果,而且是在不改变整个程序行为的前提下替换的,那么我们就说这段代码是引用透明的
基于使用纯函数输入相同输出相同的原则,我们可以得出——函数式编程是具有引用透明性的。
柯里化(curry)
柯里化(curry):只传递给函数一部分参数来调用它,让它返回一个函数去处理剩下的参数。
下面是一段典型的 curry 函数的例子
1 2 3 4 5 6 7 8 9 10 11 12
| var add = function(x) { return function(y) { return x + y; }; }; var increment = add(1); var addTen = add(10); increment(2); addTen(2);
|
同样,我们也可以借助工具完成函数 curry 化的工作,以下的例子利用 Lodash 库的 curry 方法
可以先大致看一下 Lodash 中 curry 的使用方法
1 2 3 4 5 6 7 8 9 10 11
| const curry = _.curry; let func = (x, y) => { return [x, y, x + y]; } let curryFunc = curry(func); let a = curryFunc(1)(2); let b = curryFunc(1, 2);
|
把我们需要 curry 化的函数传入 curry,返回的函数就被 curry 化处理了,我们可以把参数任意的组合分次传入。
这样,传入一个或者几个参数就能得到一个新的函数。
函数组合(compose)
下面这段代码就是一个函数组合(compose)
1 2 3 4 5
| var compose = function(f,g) { return function(x) { return f(g(x)); }; };
|
compose 使我们像搭乐高积木一样使用函数,随意选择两个函数,即可组合成一个新的函数,x 使数据在两个函数间传输。
compose 的一个例子
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| var head = function(x) { return x[0]; }; var reverse = function(x) { return x.reverse()} var toLowerCase = function(x) { return x.toLowerCase()} var last = compose(head, reverse); var result = last(['jumpkick', 'roundhouse', 'uppercut']); console.log(result); var snakeCase = compose(toLowerCase, head); var result1 = snakeCase(["Q", "W", "E"]); console.log(result1);
|
从这个例子可以发现,组合后是一个“从右向左”的过程
Lodash 为我们提供两种组合方式,分别是“从右向左”和“从左向右”
1 2 3 4 5 6 7 8 9 10 11
| function square(n) { return n * n; } var addSquare = _.flowRight([square, _.add]); addSquare(1, 2); var addSquare = _.flow([_.add, square]); addSquare(1, 2);
|
用 Lodash 对上面的函数进行一下组合
1 2 3 4
| const composeRight = _.flowRight; let newFunc = composeRight(toLowerCase, head, reverse); let result = newFunc(["Q", "W", "E"]); console.log(result);
|
可以看到,组合就像一个流水线一样,我们用函数指定分别做什么样的操作,经过流水线的加工,最后得出结果,具有很好的语义化。
参考