持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第13天,点击查看活动详情
前言
compose 翻译成中文是 组成、构成的意思,而对于函数而言,compose 函数用于将多个函数组成新的函数,然后按照一定的顺序执行,并且,前一个函数的返回值作为下一个函数的参数。
对于 JS 而言,并没有提供现成的 compose 函数,需要引入第三方库或者手动实现。
compose 的作用
假设存在以下几个函数:
/** 数字加一 */
function addOne (num) {
return num + 1
}
/** 数字减去十 */
function subTen (num) {
return num - 10
}
/** 数字乘以二 */
function double (num) {
return num * 2
}
/** 数组除以三 */
function divThree (num) {
return num / 3
}
现在,需要按照一定的逻辑顺序执行以上函数,那么一般写法如下;
console.log('result:', double(subTen(addOne(divThree(9)))))
// result: -12
这种写法主要有两个缺点:
- 不可重复利用,如果这段逻辑需要执行多次,那么需要多次写出
double(subTen(addOne(divThree(9))));- 嵌套层数过多,不利于阅读和修改;
如果使用了 compose 函数,那么只需要先使用 compose 将指定的函数按照顺序组成新的函数,直接执行该函数便可以得到想要的结果,代码如下:
// 组成新的函数
const compose1 = compose(divThree, addOne, subTen, double)
// 执行新的函数
console.log('compose1 result:', compose1(9))
// compose1 result: -12
这样的代码易于阅读和修改,并且可以重复利用。
实现 compose
实现 compose 的基本思路也很简单,把传入的函数按照顺序放入数组中,遍历数组,执行数组中的每一项,将执行的返回值保存起来,作为下一个函数的参数,如此循环,直至数组遍历完成。
看上去很简单,但实际写代码有几个小细节需要注意,具体步骤如下:
- 定义
compose函数,传入的参数必须是函数;- 使用
...fns接受全部的参数,同时也将参数存入了数组中,方便后续的遍历;- 如果不存在参数
fns,则会返回默认函数;- 检测每个参数是都为函数类型,如果有一个不是,则报错;
- 返回新的函数
returnFunction,该函数接受的参数使用...arg存储,同时也是一个闭包,只要该函数可以访问上下文中fns:
5.1 开始遍历函数数组fns,取出当前需要执行的函数fn;
5.2 传入参数,执行函数的数组:
5.2.1 如果是第一个函数,其参数为returnFunction接受到的参数arg,因为arg被转换为数组,所以需要手动拆分一下,代码为fn(...arg),将其返回值value保存起来;
5.2.2 如果不是第一个函数,取上一个函数的返回值作为参数,执行fn(value), 将其返回值value保存起来;
5.3 回到 5.1,直到fns被遍历完成为止;
5.4 返回最终的执行结果。
完整实现代码如下:
/**
* 函数组合
* @param { function[] } fns 参数必须全是数组
*/
function compose (...fns) {
// 不存在参数,返回默认函数
if (fns.length === 0) return num => num
// 参数检测
const nonFunction = fns.find(fn => typeof fn !== 'function')
if (nonFunction) {
throw new TypeError(`${nonFunction} is not a function !`)
}
// 参数符合要求,返回一个组合函数,本质上是按顺序调用传入的函数
return (...arg) => {
return fns.reduce((params, fn) => {
// arg 作为参数被手动转换为数组,如果接受的参数是 arg, 必须拆分传入
return params === arg ? fn(...params) : fn(params)
}, arg)
}
}
其中遍历 fns 的方式有很多,本文使用 reduce 进行遍历,这样可以直接返回最终的值,相比于其他遍历方法,省去了一些代码,可阅读性也好。
对于 fns 的类型检测,也可以放在最后的遍历 fns 中执行;虽然这样看上去少了检测的循环遍历,提高了性能,但是这样只要在执行创建的函数时,才能检测到错误,而且每次执行创建的函数都需要检测 fns,反而提高了执行时间,也没必要多次检测 fns。