JS手写函数柯里化

117 阅读2分钟

通过手写柯里化函数,可以充分理解闭包的使用场景,arguments伪数组,以及一些关于原型的底层知识。

比如实现一个功能,给到这么一个函数,将所有参数相加最终返回来执行结果(此函数返回结果为55) currying(1, 2, 3)(4, 5, 6)(7, 8, 9, 10),如该怎么做呢,仔细分析需要做到以下几点?

  • 一个函数可以接收多个参数,被反复调用
  • 是一个参数个数不固定的函数
  • 每次执行应该是返回一个函数供后续调用
  • 参数缓存(即前面的参数会缓存到最后,不会被后面函数调用时的参数覆盖掉)

通过以上分析,代码实现如下

function currying(){
    //arguments是伪数组,有数组的属性却不包含数组的方法,通过以下方式将其改变成一个真正的数组
    //slice是数组原型上的方法,不传参时会返回一个新数组
    //apply(或call)改变this指向
    //数组方法依赖于内部this数据容器来执行,因此传入arguments会返回一个新数组
    const args = Array.prototype.slice.apply(arguments) 
    const inner = function inner(){
          args.push(...arguments) //此处与父函数形成闭包,用于缓存参数
          return inner //每次调用返回inner函数,才可以重复调用
    }
    return inner  //第一次调用返回inner函数
}

此时已经实现了参数缓存以及函数的连续调用。那如何返回计算的结果呢?因为inner函数必须返回一个函数才能连续调用,而JS函数里却只能有一个return。此时可以给inner函数添加一个自定义的getValue方法,如下:

function currying(){ 
    const args = Array.prototype.slice.apply(arguments)
    const inner = function inner(){
        args.push(...arguments) 
        return inner
    } 
    inner.getValue = ()=>{
        console.log(args) // [1,2,3,4,5,6,7,8,9,10]
        return args.reduce((pre, cur)=> pre + cur, 0)
    }
    return inner 
}
const res = currying(1, 2, 3)(4, 5, 6)(7, 8, 9, 10);
console.log(res.getValue()) // 55

以上基本上实现了一开始提到的几个功能,不过如果想要允许函数有返回值进行二次计算,比如想得到 res - 1 的值就得额外调用一个getValue方法,用res.getValue() - 1 替代,所以可以进行以下改造:

function currying(){ 
    const args = Array.prototype.slice.apply(arguments)
    const inner = function inner(){ 
        args.push(...arguments) 
        return inner 
    }
    inner.__proto__[Symbol.toPrimitive] = function (){
      return args.reduce((pre, cur)=> pre + cur, 0)
    }
    return inner
} 
const res = currying(1, 2, 3)(4, 5, 6)(7, 8, 9, 10); 
console.log(res - 1)