函数式编程
定义:
简单说,"函数式编程"是一种"编程范式"(programming paradigm),也就是如何编写程序的方法论。 它属于"结构化编程"的一种,主要思想是把运算过程尽量写成一系列嵌套的函数调用。
(1 + 2) * 3 - 4
// 传统的过程式编程
var a = 1 + 2;
var b = a * 3;
var c = b - 4;
// 函数式编程
var result = subtract(multiply(add(1,2), 3), 4);
2
3
4
5
6
7
8
9
特点:
函数是第一等公民
所谓"第一等公民"(first class),指的是函数与其他数据类型一样,处于平等地位,可以赋值给其他变量,也可以作为参数,传入另一个函数,或者作为别的函数的返回值。
只用表达式,不用语句
"表达式"(expression)是一个单纯的运算过程,总是有返回值;"语句"(statement)是执行某种操作,没有返回值。函数式编程要求,只使用表达式,不使用语句。也就是说,每一步都是单纯的运算,而且都有返回值。 原因是函数式编程的开发动机,一开始就是为了处理运算(computation),不考虑系统的读写(I/O)。"语句"属于对系统的读写操作,所以就被排斥在外。 当然,实际应用中,不做I/O是不可能的。因此,编程过程中,函数式编程只要求把I/O限制到最小,不要有不必要的读写行为,保持计算过程的单纯性。
没有副作用
所谓"副作用"(side effect),指的是函数内部与外部互动(最典型的情况,就是修改全局变量的值),产生运算以外的其他结果。 函数式编程强调没有"副作用",意味着函数要保持独立,所有功能就是返回一个新的值,没有其他行为,尤其是不得修改外部变量的值。
不修改状态 函数式编程使用参数保存状态,最好的例子就是递归。
function reverse(string) {
if(string.length == 0) {
return string;
} else {
return reverse(string.substring(1, string.length)) + string.substring(0, 1);
}
}
2
3
4
5
6
7
引用透明
指的是函数的运行不依赖于外部变量或"状态",只依赖于输入的参数,任何时候只要参数相同,引用函数所得到的返回值总是相同的。
compose
函数式编程中有一个比较重要的概念就是函数组合(compose),组合多个函数,同时返回一个新的函数。调用时,组合函数按顺序从右向左执行。右边函数调用后,返回的结果,作为左边函数的参数传入,严格保证了执行顺序,这也是compose 主要特点。
组合两个函数
function compose (f, g) {
return function(x) {
return f(g(x));
}
}
var arr = [1, 2, 3],
reverse = function(x){ return x.reverse()},
getFirst = function(x) {return x[0]},
compseFunc = compose(getFirst, reverse);
compseFunc(arr); // 3
2
3
4
5
6
7
8
9
10
11
12
组合任意函数
reduce实现compose函数
const greetings = (name, word) => `${name}, ${word}`
// 大写
const toUpper = x => x.toUpperCase()
// 增加标点
const addPunctuate = s => s + '!'
function compose (...functions) {
if (functions.length === 0) {
return functions[0]
} else {
return functions.reduce((result, item) => {
return (...args) => result(item(...args))
})
}
}
console.log(compose(addPunctuate, toUpper, greetings)('tom', '你好')) // TOM,你好!
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
通过call和apply方法实现compose函数
const greetings = (name, word) => `${name}, ${word}`
// 大写
const toUpper = x => x.toUpperCase()
// 增加标点
const addPunctuate = s => s + '!'
compose = (...fns)=>{
if (fns.length === 1) {
return fns[0]
} else {
return (...args)=>{
var index = 0
var result = fns[index].apply(this,args)
while (++index<fns.length){
result = fns[index].call(this,result)
}
return result
}
}
}
console.log(compose(greetings,addPunctuate,toUpper,)('tom', '你好'))
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
函数柯里化 currying
Currying 为实现多参函数提供了一个递归降解的实现思路——把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数而且返回结果的新函数,在某些编程语言中(如 Haskell),是通过 Currying 技术支持多参函数这一语言特性的。
总结:Currying ——只传递给函数一部分参数来调用它,让它返回一个函数去处理剩下的参数。
使用示例:
<div onClick={currying(handleOnClick, data)} />
两个函数的柯里化
function curriedAdd (x) {
return function(y) {
return x + y
}
}
2
3
4
5
6
7
8
9
多个函数的柯里化
function add(a, b, c, d) {
return a + b + c + d
}
const curryedAdd = curry(add,1,2)
console.log(curryedAdd(3)(100))
function curry(fn) {
let arg = [].slice.call(arguments,1)
if (arg.length >= fn.length) {
return fn(...arg)
}
return function (...args2) {
return curry(fn, ...arg, ...args2)
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
性能对比
结果为: 箭头函数 > bind > currying
结语
- Currying 在 JavaScript 中是“低性能”的,但是这些性能在绝大多数场景,是可以忽略的。
- Currying 的思想极大地助于提升函数的复用性。
- Currying 生于函数式编程,也陷于函数式编程。假如没有准备好写纯正的函数式代码,那么 Currying 有更好的替代品。