函数式编程可以理解为以函数作为主要载体的编程方式,用函数去拆解、抽象一般的表达式
在函数式编程中,函数就是一个管道(pipe)。这头进去一个值,那头就会出来一个新的值,没有其他作用
与命令式相比,这样做的好处在哪?主要有以下几点:
-
语义更加清晰
-
可复用性更高
-
可维护性更好
-
作用域局限,副作用少
compose
如果一个值要经过多个函数,才能变成另外一个值,就可以把所有中间步骤合并成一个函数,这叫做"函数的合成"(compose)
const compose = function (f, g) {
return function (x) {
return f(g(x));
};
}
![](https://img.haomeiwen.com/i1559594/39272c5e5c7a647d.png)
上图中,X和Y之间的变形关系是函数f,Y和Z之间的变形关系是函数g,那么X和Z之间的关系,就是g和f的合成函数g·f。
函数的合成还必须满足结合律。
![](https://img.haomeiwen.com/i1559594/ad0eb28e7ecfa0f9.png)
compose(f, compose(g, h))
// 等同于
compose(compose(f, g), h)
// 等同于
compose(f, g, h)
compose实现思路就是先把传入的函数都缓存起来,然后在传入数据的时候,再挨个的使用apply执行函数, 上一个函数的输出数据,作为下一个函数的输入数据
compose遵循的是从右向左运行,而不是由内而外运行。也就是说compose是从最后一个函数开始执行
const compose = function() {
const args = arguments;
let start = args.length - 1;
return function() {
let i = start;
const result = args[start].apply(this, arguments);
while (i--) result = args[i].call(this, result);
return result;
};
};
函数就像数据的管道(pipe)。那么,函数合成就是将这些管道连了起来,让数据一口气从多个管道中穿过。组合让我们的代码简单而富有可读性。
curry
f(x)和g(x)合成为f(g(x)),有一个隐藏的前提,就是f和g都只能接受一个参数。如果可以接受多个参数,比如f(x, y)和g(a, b, c),函数合成就非常麻烦。
这时就需要函数柯里化了。所谓"柯里化",就是把一个多参数的函数,转化为单参数函数。
// 柯里化之前
function add(x, y) {
return x + y;
}
add(1, 2) // 3
// 柯里化之后
function addX(y) {
return function (x) {
return x + y;
};
}
addX(2)(1) // 3
柯里化实现思路就是当传入的参数个数没有到达func函数要求的参数个数的时候一直返回柯里化函数。 只有参数满足func函数的个数的时候才通过apply执行func函数
//不考虑函数的个数,简单的实现
function curry(fn) {
// 获取第一个参数以后的参数,也就是除了fn以后的参数
const args = [].slice.call(arguments, 1);
return function() {
//将当前函数和后面的函数的参数加起来
var newArgs = args.concat([].slice.call(arguments));
//将所有函数参数加起来并传入fn执行
//不管this,目的就是执行fn
return fn.apply(this, newArgs);
};
};
//考虑到参数个数
function curry(func , thisArg){
//将所有参数收到thisArg
if ( !Array.isArray(thisArg) ) {
thisArg = [];
}
return function(){
let args = Array.prototype.slice.call(arguments);
if ( (args.length+thisArg.length) < func.length ) {
return curry(func , thisArg.concat(args));
}
return func.apply(this , thisArg.concat(args));
};
}
说个题外话,bind就是利用柯里化实现的,只不过两者目的不一样,柯里化是收集所有除参数函数的其他参数,然后执行参数函数,而bind是将第一个参数设为this,然后将所有其他参数放到参数函数里面执行,柯里化不考虑this的问题,而bind需要考虑this的问题,下面是bind的粗略实现
Function.prototype.bind = Function.prototype.bind ||
function(context){
var self = this
var args = Array.prototype.slice.call(arguments, 1)
return function(){
var innerArgs = Array.prototype.slice.call(arguments);
var finalArgs = args.concat(innerArgs);
//绑定context,确定this
return self.apply(context,finalArgs);
}
}
function add(a, b) {
return a + b;
}
var addCurry = curry(add, 1, 2);
addCurry() // 3
//或者
var addCurry = curry(add, 1);
addCurry(2) // 3
//或者
var addCurry = curry(add);
addCurry(1, 2) // 3
有了柯里化以后,我们就能做到,所有函数只接受一个参数。
pure function(转载)
一个函数的返回结果只依赖于它的参数,并且在执行过程里面没有副作用,我们就把这个函数叫做纯函数。
函数的返回结果只依赖于它的参数
const a = 1
const foo = (b) => a + b
foo(2) // => 3
foo 函数不是一个纯函数,因为它返回的结果依赖于外部变量 a,我们在不知道 a 的值的情况下,并不能保证 foo(2) 的返回值是 3。虽然 foo 函数的代码实现并没有变化,传入的参数也没有变化,但它的返回值却是不可预料的,现在 foo(2) 是 3,可能过了一会就是 4 了,因为 a 可能发生了变化变成了 2。
const a = 1
const foo = (x, b) => x + b
foo(1, 2) // => 3
现在 foo 的返回结果只依赖于它的参数 x 和 b,foo(1, 2) 永远是 3。今天是 3,明天也是 3,在服务器跑是 3,在客户端跑也 3,不管你外部发生了什么变化,foo(1, 2) 永远是 3。只要 foo 代码不改变,你传入的参数是确定的,那么 foo(1, 2) 的值永远是可预料的。
这就是纯函数的第一个条件:一个函数的返回结果只依赖于它的参数。
函数执行过程没有副作用
一个函数执行过程对产生了外部可观察的变化那么就说这个函数是有副作用的。
我们修改一下 foo:
const a = 1
const foo = (obj, b) => {
return obj.x + b
}
const counter = { x: 1 }
foo(counter, 2) // => 3
counter.x // => 1
我们把原来的 x 换成了 obj,我现在可以往里面传一个对象进行计算,计算的过程里面并不会对传入的对象进行修改,计算前后的 counter 不会发生任何变化,计算前是 1,计算后也是 1,它现在是纯的。但是我再稍微修改一下它:
const a = 1
const foo = (obj, b) => {
obj.x = 2
return obj.x + b
}
const counter = { x: 1 }
foo(counter, 2) // => 4
counter.x // => 2
现在情况发生了变化,我在 foo 内部加了一句 obj.x = 2,计算前 counter.x 是 1,但是计算以后 counter.x 是 2。foo 函数的执行对外部的 counter 产生了影响,它产生了副作用,因为它修改了外部传进来的对象,现在它是不纯的。
但是你在函数内部构建的变量,然后进行数据的修改不是副作用:
const foo = (b) => {
const obj = { x: 1 }
obj.x = 2
return obj.x + b
}
虽然 foo 函数内部修改了 obj,但是 obj 是内部变量,外部程序根本观察不到,修改 obj 并不会产生外部可观察的变化,这个函数是没有副作用的,因此它是一个纯函数。
除了修改外部的变量,一个函数在执行过程中还有很多方式产生外部可观察的变化,比如说调用 DOM API 修改页面,或者你发送了 Ajax 请求,还有调用 window.reload 刷新浏览器,甚至是 console.log 往控制台打印数据也是副作用。
纯函数很严格,也就是说你几乎除了计算数据以外什么都不能干,计算的时候还不能依赖除了函数参数以外的数据。
为什么要煞费苦心地构建纯函数?因为纯函数非常“靠谱”,执行一个纯函数你不用担心它会干什么坏事,它不会产生不可预料的行为,也不会对外部产生影响。不管何时何地,你给它什么它就会乖乖地吐出什么。如果你的应用程序大多数函数都是由纯函数组成,那么你的程序测试、调试起来会非常方便
参考链接:
网友评论