函数是一等公民!
函数作为参数
[1, 2, 3, 4].map(function(x) {
return ++x;
});
可以看到函数 function(x){return x++} 是作为参数被传入 Array 的 map 方法中。map 是函数式编程最常见的标志性函数.
函数作为输出
- 柯里化(curry)
我们把一个多参的函数变成一次只能接受一个参数的函数的过程叫做柯里化。如:
const f = (a, b, c d) => { ... }
const curried = curry(f)
curried(a, b, c, d)
curried(a, b, c)(d)
curried(a)(b, c, d)
curried(a, b)(c, d)
curried(a)(b, c)(d)
curried(a)(b)(c, d)
curried(a, b)(c)(d)
// ...
// 这些函数执行结果都一样
纯函数
纯函数是一种函数,它不改变程序的外部状态,也不会造成数据可变性。纯函数的输出完全依赖于它的输入值,对于相同的输入,永远返回相同的输出。
敌不动,我不动,敌若动,我仍不动!这就是不产生副作用。 不依赖于外部状态。对于测试和debug都是很方便的!
var xs = [1,2,3,4,5];
// 纯的
xs.slice(0,3);
//=> [1,2,3]
xs.slice(0,3);
//=> [1,2,3]
xs.slice(0,3);
//=> [1,2,3]
// 不纯的
xs.splice(0,3);
//=> [1,2,3]
xs.splice(0,3);
//=> [4,5]
xs.splice(0,3);
//=> []
函数组合
- compose
函数组合指把多个函数通过嵌套的方式,把函数作为入参传入,把函数作为返回值传出,组合成为一个全新的函数,形成管道数据流。
const add1 = (x) => x + 1
const mul3 = (x) => x * 3
const div2 = (x) => x / 2
const operate = compose(div2, mul3, add1, add1)
operate(0) // => 相当于 div2(mul3(add1(add1(0))))
operate(2) // => 相当于 div2(mul3(add1(add1(2))))
最常用的函数式编程库
- lodash
- ramda
函子
比如functor/monad,我的理解是这样的:对封闭对象中的状态进行操作,比如多个独立的瓶子以及每个瓶子中装有各自的船,函子的作用就是可以将多个瓶子融合成一个瓶子,原来瓶子中的船便会在最终瓶子中成为多艘船的融合。当然这个条件是船的类型是一致的。更多的了解可以参考有关文章。
FP的哈哈
我们完全可以在日常工作中将函数式编程作为一种辅助手段,在条件允许的前提下,借鉴函数式编程中的思路,例如:
- 多使用纯函数减少副作用的影响。
- 使用柯里化增加函数适用率。
- 使用 Pointfree 编程风格,减少无意义的中间变量,让代码更且可读性。
- ……
FP的呵呵哒
-
性能:函数式编程相对于指令式编程,性能绝对是一个短板,因为它往往会对一个方法进行过度包装,从而产生上下文切换的性能开销。同时,在 JS 这种非函数式语言中,函数式的方式必然会比直接写语句指令慢(引擎会针对很多指令做特别优化)。就拿原生方法 map 来说,它就要比纯循环语句实现迭代慢 8 倍。
-
资源占用:在 JS 中为了实现对象状态的不可变,往往会创建新的对象,因此,它对垃圾回收(Garbage Collection)所产生的压力远远超过其他编程方式。这在某些场合会产生十分严重的问题。
-
递归陷阱:在函数式编程中,为了实现迭代,通常会采用递归操作,为了减少递归的性能开销,我们往往会把递归写成尾递归形式,以便让解析器进行优化。但是众所周知,JS 是不支持尾递归优化的(虽然 ES6 中将尾递归优化作为了一个规范,但是真正实现的少之又少)
不要那么悲观
redux/rxjs的一些设计思想来源于函数式编程~