1、前言
在之前探讨redux的中间件的时候,applyMiddleware源码中有遇到过compose()函数,当时不太明白起作用,所以就上网好好查了一下,做了个总结。
2、普通函数
在函数式编程当中有一个很重要的概念就是函数组合
, 实际上就是把处理数据的函数像管道一样连接起来, 然后让数据穿过管道得到最终的结果。
例1:
const fn1 = (x) => x + 10;
const fn2 = (x) => x * 10;
const fn3 = (x) => x - 10;
console.log(fn3(fn2(fn1(1)))) ===> 100
我们想输出的是一个多层函数嵌套
的运行结果,即把前一个函数的运行结果赋值给后一个函数
。但是如果需要嵌套多层函数,那这种类似于f(g(h(x)))
的写法可读性太差,我们考虑能不能写成(f, g, h)(x)
这种简单直观的形式,于是compose()函数就正好帮助我们实现。
3、compose函数
3.1、compose的概念
先介绍一下compose()函数的概念
- 概念:
将需要嵌套执行的函数扁平化处理
。嵌套执行
指的是,一个函数的返回值将作为另一个函数的参数
- 作用:实现函数式编程中的 pointfree 风格(无参数),使我们专注于【转换】而不是【数据】
- 实现:接收多个函数作为参数,
从右到左
,一个函数的输入为另一个函数的输出 - 意义:编程更精练、算法更清晰、无参数干扰
- 威力:【任意组合】
- 缺点:不能直观的看到参数
3.2、compose的实现
首先,我们先看一下参数,funcs
就是需要给出不确定个数的具体函数
:
function compose(...funcs) {
//=>funcs:传递的函数集合
}
接着,我们看到compose函数执行后跟个(),说明外层函数执行完会再执行一个函数
,即函数执行完会返回一个新函数
,而且也会给出第一次调用函数时的参数
:
function compose(...funcs) {
//=>funcs:传递的函数集合
return function proxy(...args) {
//=>args:第一次调用函数传递的参数集合
}
}
紧接着,我们需要判断
给出的函数集合的个数
:
- 如果
没有给函数
,我们只需将后一个的参数返回; - 如果只给出
一个函数
,我们只需把后一个的参数赋给这个函数去执行即可:
function compose(...funcs) {
//=>funcs:传递的函数集合
return function proxy(...args) {
//=>args:第一次调用函数传递的参数集合
let len = funcs.length;
if (len === 0) {
//=>一个函数都不需要执行,直接返回参数args
return args;
}
if (len === 1) {
//=>只需要执行第一个函数,把函数执行,把其结果返回即可
return funcs[0](...args);
}
};
}
- 如果给出的函数参数集合是
两个及以上
,那就是把前一个函数的执行结果赋给后一个函数。说到这,应该会想到一个满足这个需求的数组方法——reduce:
function compose(...funcs) {
//=>funcs:传递的函数集合
return function proxy(...args) {
//=>args:第一次调用函数传递的参数集合
let len = funcs.length;
if (len === 0) {
//=>一个函数都不需要执行,直接返回参数args
return args;
}
if (len === 1) {
//=>只需要执行第一个函数,把函数执行,把其结果返回即可
return funcs[0](...args);
}
return funcs.reduce((x, y) => {
//TODO
});
};
}
最后,我们需要注意的是,第一次执行
的时候,参数x是个函数
,之后
再执行的时候x是个函数执行的结果
,所以需要进行判断:
function compose(...funcs) {
//=>funcs:传递的函数集合
return function proxy(...args) {
//=>args:第一次调用函数传递的参数集合
let len = funcs.length;
if (len === 0) {
//=>一个函数都不需要执行,直接返回ARGS
return args;
}
if (len === 1) {
//=>只需要执行一个函数,把函数执行,把其结果返回即可
return funcs[0](...args);
}
return funcs.reduce((x, y) => {
return typeof x === "function" ? y(x(...args)) : y(x)
});
};
}
这样,我们就完成了compose函数,我们将开始普通函数的案例改为compose函数:
unction compose(...funcs) {
//=>funcs:传递的函数集合
console.log('---funcs---',funcs)
return function proxy(...args) {
//=>args:第一次调用函数传递的参数集合
let len = funcs.length;
if (len === 0) {
//=>一个函数都不需要执行,直接返回args
return args;
}
if (len === 1) {
//=>只需要执行一个函数,把函数执行,把其结果返回即可
return funcs[0](...args);
}
return funcs.reduce((x, y) => {
console.log('--x--',x)
console.log('--y--',y)
return typeof x === "function" ? y(x(...args)) : y(x)
});
};
}
const fn1 = (x) => x + 10;
const fn2 = (x) => x * 10;
const fn3 = (x) => x - 10;
let result = compose(fn3, fn2, fn1)(1);
console.log('--result--',result);
- 我们看出,
第一次执行时,x是一个函数;第二次执行时,x是一个常数
- 但是我们发现执行的结果是
从左到右
,即fn3 ——> fn2 ——> fn1
,并不是我们期望的从右向左。所以我们可以使用reduceRight()
或者reverse()
帮助我们实现
我们将reduce
改为reduceRight
或reverse()
:
function compose(...funcs) {
//=>funcs:传递的函数集合
console.log('---funcs---',funcs)
return function proxy(...args) {
//=>args:第一次调用函数传递的参数集合
let len = funcs.length;
if (len === 0) {
//=>一个函数都不需要执行,直接返回args
return args;
}
if (len === 1) {
//=>只需要执行一个函数,把函数执行,把其结果返回即可
return funcs[0](...args);
}
//方式一
return funcs.reduceRight((x, y) => {
console.log('--x--',x)
console.log('--y--',y)
return typeof x === "function" ? y(x(...args)) : y(x)
});
//方式二
return funcs.reverse().reduce((x, y) => {
console.log('--x--',x)
console.log('--y--',y)
return typeof x === "function" ? y(x(...args)) : y(x)
});
};
}
const fn1 = (x) => x + 10;
const fn2 = (x) => x * 10;
const fn3 = (x) => x - 10;
let result = compose(fn3, fn2, fn1)(1);
console.log('--result--',result);
- 这次,我们就实现了最初普通函数时这种
(fn3(fn2(fn1(1))))
嵌套逻辑,即利用compose函数compose(fn3, fn2, fn1)(1)
4、redux源码中的compose
在redux源码中compose有这样一种写法:
function compose(...funcs) {
if (funcs.length === 0) {
return arg => arg
}
if (funcs.length === 1) {
return funcs[0]
}
return funcs.reduce((a, b) => (...args) => a(b(...args)))
}
这种写法更简便,不用对第一个参数函数进行判断,与我们自己写compose函数的区别
就是:x(y)还是y(x)
,这点我们要注意
同样,我们用刚才的案例测试redux源码中的compose函数:
function compose(...funcs) {
if (funcs.length === 0) {
return arg => arg
}
if (funcs.length === 1) {
return funcs[0]
}
console.log('---funcs---',funcs)
return funcs.reduce((a, b) => (
console.log('--a--',a),
console.log('--b--',b),
(...args) => a(b(...args))
))
}
const fn1 = (x) => x + 10;
const fn2 = (x) => x * 10;
const fn3 = (x) => x - 10;
let result = compose(fn3, fn2, fn1)(1);
console.log('--result--',result);
本文参考博客: