函数柯里化的定义
函数柯里化(Currying)是一种高级函数的用法,其可以把接收多个参数的函数变成接收单个参数并且返回接受余下的参数而且返回结果的新函数
现在有一个 add 函数,其作用是返回所有参数的和,调用方法如下
add(1, 2, 3, 4, 5) // 返回 15
现在我们要把该函数的调用方式变成另一种,如下列代码
add(1)(2)(3)(4)(5) // 返回 15add(1)(2, 3, 4)(5) // 返回 15
首先说明一下,参数的个数不是固定的,可以是1个,也可以是上千个
看到这里,你能想到有什么办法能实现上述需求吗?这里我给出了三种实现方式,一起来看一下
方法一
先来看看第一种方式的完整代码
function add() { // 创建数组,用于存放之后接收的所有参数 let args = [...arguments] // 设置一个递归调用的函数,每次调用将传进来的参数添加到args中,并返回该函数等待下一次调用 function getArgs() { args.push(...arguments) return getArgs } // 重新定义getArgs的toString函数,用于最后一次调用完该函数后展示所有参数之和 getArgs.toString = function() { return args.reduce((a, b) => { return a + b }) } return getArgs}
首先,我们根本不知道之后会传入多少个参数,因此我们无法确认递归函数何时结束,但我们却通过递归函数将求和结果延时了,即不管我们调用几次add函数,都不会返回求和结果,但此时如何让add函数知道我们已经传完参数了呢?
这就要涉及一个非常冷门的知识了,我们来看一下
先简单定义一个add函数,函数内将自身返回,代码如下
function add() { let str = '我是add函数' return add}let res = add()console.log(res);console.log(typeof res)
我们来看看在浏览器中,会打印什么结果,结果如下图
可以看到,add函数返回的结果类型是function类型,但在浏览器中被隐式地转化为字符串了。
其实这是调用了add函数的toString方法,将toString方法的返回值作为隐式转化后的结果才实现的,不信我们修改代码来实现一下
function add() { let str = '我是add函数' return add}add.toString = function() { return '我是add函数的toString方法'}let res = add()console.log(res);console.log(typeof res);
然后再来看看浏览器中的打印结果
因此我们就可以利用该特性,将求和的代码写在add函数的toString方法中,那么在最后一次调用add函数后,返回的就是所有参数的和了
缺点: 不过此方法也有所缺点,那就是返回的结果前面会有一个 f 表示其原本是函数类型的只是隐式转化成了字符串
方法二
第二种方法是通过判断有无传入参数来确认是否是最后一次调用,我们先来看看完整代码
function add() { // 先将第一次调用的所有参数存放在args中 let args = [...arguments] return function() { // 若没有传入参数,则直接将args中存放的所有值求和并返回 if(arguments.length == 0) { return args.reduce((a, b) => { return a + b }) } // 若有传入参数,则将参数存放到args中,并继续返回该函数接收下一次调用 else { let _args = [...arguments] for(let i = 0; i < _args.length; i++) { args.push(_args[i]) } return arguments.callee } }}
这个方法实现的思路应该是很简单的,我就不做多余的讲解了,直接来看看如何使用的吧
add(1)(2)(3)(4)(5)() // 返回 15add(1)(2)(3)(4)(5) // 返回了一个匿名函数
缺点: 该方法的缺点就是要在最后再自调用一次,表示传参完毕,这样才能获得之前传入所有参数的和
方法三
第三种方法是在传参之前先设定自己需要传入的参数个数 n,然后函数内部会自动判断你总共传入的参数个数,当等于 n 时,则自动求和并返回,我们来看一下代码
function curry(length) { // 第一个参数是剩余传入参数个数,因此不用存放在args中 let args = [...arguments].slice(1) return function() { // 将此次传入的参数与之前所有的参数合并 args = args.concat([...arguments]) // 可传入参数个数大于0 if(arguments.length < length) { // 返回curry函数,并将第一个参数length减去传入参数的个数,表示剩余可穿参数个数 return curry.apply(this, [length - arguments.length].concat(args)) } // 可传入参数个数为0 else { // 求和并返回 return args.reduce((a, b) => a + b) } }}
该方法的思想很奇妙,就是在一次调用以后,判断剩余的可传入参数个数 length,若还能继续传入参数就重新调用一次 curry 函数,把 length 和 之前所有传过来的参数都作为其参数传入
来看一下使用方式吧
let add = curry(5)add(1)(2)(3)(4)(5) // 返回 15
缺点:每次都要先设定传入参数的个数,不太灵活
END
我是Lpyexplore,一个前端的探索者。