函数柯里化解决JavaScript常考题之n数的和

2,114 阅读5分钟

什么是柯里化

先看一个例子🌰:

function add(a, b){
    return a + b
}
add(12//3
add(15//6
//柯里化
function addCurry(a){
    return function (b){
        return a + b
    }
}
let a = addCurry(1//固定1
a(2//3
a(5//6

可以看出如果函数实现的需求中形参a是不怎么变化的,不把函数柯里化的话在调用时就需要对形参a一直重复传参。所以柯里化就是将多个函数的参数拆分为一个函数参数调用的形式。

JavaScript高级程序设计中写到函数柯里化的基本方法:使用一个闭包返回一个函数。

所以大概就延续这个思路去解决n数之和的问题。

解决n数之和sum(1)(2)...(n)

这个问题肯定不能按照上面的例子那样写的。需要用到递归调用才能解决。

function sum(a){
    retutn (b) {
        retutn (c){
            .....
        }
    }
}

首先我们知道使用闭包返回一个函数。但是针对n数之和,这个函数应该要保存住每一次调用的值并且反复调用才有可能实现。但是我们不知道实际需求n是多少,所以需要想出方法让函数在正确的地方停下来输出正确的值。

1.以valueOf结束

valueOfObject的原型方法,它和toString()有一些相似,都会在后台进行隐式调用。都知道如果对两个变量进行相等(=====)比较变量会进行隐式转换。这个valueOf也会隐式调用,而且可以改写valueOf方法。

在本例中:用valueOf作为函数停下的标志,当调用了就返回最终结果。所以改写valueOf方法返回最终结果。

function sum(a){
    let result = a;
    function add(b){
        result += b;
        return add;
    }
    add.valueOf = function(){
        return result;
    }
    return add;
}
// sum(1)(2)本来返回函数,但是遇到 + 就隐式调用了valueOf
sum(1)(2) + 3 //6  
sum(1)(2).valueOf() //3

这种写法的问题在于如果不进行计算的话sum(1)(2)...返回的是函数,必须要在最后加上valueOf方法才能得出正确值。

2.以无参数的函数调用结束

最后没有任何参数的函数作为函数结束的标志。关键在于arguments里有没有参数,没有参数就返回最终结果。

function sum(a) {
  let result = a;
  return function add(b) {
    if (arguments.length) { //如果有参数就返回add函数继续调用
      result += b;
      return add;
    }
    return result;// 如果没有参数就返回最终结果
  };
}
sum(1)(2)() //3

用ES6写更简洁(但是可能不会太好理解):

const sum = a => b => {
                return b ? sum(a + b): a 
            }

一个函数实现sum(1)(2)(3)参数变换

最终的效果要能达到以下调用都是相同结果:

  • sum(1, 2, 3)
  • sum(1)(2, 3)
  • sum(1, 2)(3)
  • sum(1)(2)(3)

1.使用arguments计数

如果arguments的长度大于等于3的话就计算结果然后返回。

function sum(...args1) {
  let args = args1;
    
  function add(...args2) {
    args = args.concat(args2);
    if (args.length >= 3) { //判断参数的长度
      return args.slice(03).reduce((pre, cur) => pre + cur, 0);
    }
    return add;
  }
    
  return add(); //只传入sum(1,2,3)时就直接调用add()函数
}

2.高阶函数

把函数作为参数传入,这样的函数称为高阶函数。

通过高阶函数写出一个通用的对固定参数的函数的柯里化函数。

function fixCurry(fn, totalArgs) {
  // 函数的长度就是定义形参的个数
  totalArgs = totalArgs || fn.length//如果不传参就设置为函数传参的个数
    
  return function recall() {
    return arguments.length < totalArgs //比较传入参数的个数与设置的参数个数是否相等
      ? recall.bind(this, ...arguments//不等就继续recall函数添加参数
      : fn.call(this, ...arguments); //相等就执行函数,然后返回结果
  };
}

let sum = fixCurry((a, b, c) => a + b + c) //返回的是recall函数
console.log(sum(1,2,3)) //6
console.log(sum(1)(2,3) === sum(1,2)(3)) //true

这个fixCurry可以用于对任何具有固定参数的函数进行柯里化处理。

参考来源Currying in JS: Answering the traditional question, Add(2)(3), which gives sum of both numbers.