函数柯里化小结

最近面试提到柯里化(Currying),虽然看了一些柯里化的文章,但从没有深入做出一些总结。所以现在开始写这一篇文章,来为它做出一些总结。

什么是函数柯里化

柯里化通常也称部分求值,其含义是给函数分步传递参数,每次传递参数后部分应用参数,并返回一个更具体的函数接受剩下的参数,这中间可嵌套多层这样的接受部分参数函数,直至返回最后结果。
因此柯里化的过程是逐步传参,逐步缩小函数的适用范围,逐步求解的过程。

柯里化函数的作用

函数柯里化允许和鼓励你分隔复杂功能变成更小更容易分析的部分。这些小的逻辑单元显然是更容易理解和测试的,然后你的应用就会变成干净而整洁的组合,由一些小单元组成的组合。

柯里化的实现依赖

  • 函数可以作为参数传递
  • 函数能够作为函数的返回值
  • 闭包

函数柯里化案例

  1. 通过JS对原始数据的转换规则
function currying(func) {
  const adder = function adder() {
    /**
     * 闭包原理
     * 通过返回函数索引闭包内变量
     * 将_args保存在闭包(_adder)内,不被回收机制回收
     * 从而反复收集参数
     * */
    const _args = []
    // _adder函数单纯只是保存参数到_args,使回收机制不对_args进行回收
    const _adder = (..._oArgs) => {
      // 无新参数返回旧值,否则计算新值
      if (_oArgs.length) {
        // apply可将数组类型转化为单一的多个参数,然后push才能对单一参数操作,而不是对数组操作
        [].push.apply(_args, [].slice.call(_oArgs))
        // valueOf 权限大于 toString,它能将数值正确反映到函数上
        _adder.toString = () => func(_args)
      }
      return _adder
    }
    return _adder()
  }
  return adder()
}

const add = currying(arr => [].reduce.call(arr, (a, b) => a + b))

console.log(add(1, 2)(3)(1, 1, 5)(5)) // 18

当一个对象转换成原始值时,先查看对象是否有valueOf方法。
如果没有valueOf 或 返回的不是原始值,那么调用toString方法,返回字符串表示
valueOf 它能覆盖 toString 的数值.

  1. 当没有参数时,一次性返回计算结果
function currying(func) {
  const adder = function adder() {
    const _args = []
    const _adder = (..._oArgs) => {
      [].push.apply(_args, [].slice.call(_oArgs))
      if (_oArgs.length === 0) {
        return func(_args)
      }
      return _adder
    }
    return _adder
  }
  return adder()
}

const add = currying(arr => [].reduce.call(arr, (a, b) => a + b))

console.log(add(1, 2)(3)(1, 1, 5)(5)()) // 18

函数柯里化的特点

柯里化(Currying)具有:延迟计算、参数复用、动态生成函数的作用。

  • 延迟计算。上面的例子已经比较好低说明了。
  • 参数复用。当在多次调用同一个函数,并且传递的参数绝大多数是相同的,那么该函数可能是一个很好的柯里化候选。
  • 动态创建函数。这可以是在部分计算出结果后,在此基础上动态生成新的函数处理后面的业务,这样省略了重复计算。或者可以通过将要传入调用函数的参数子集,部分应用到函数中,从而动态创造出一个新函数,这个新函数保存了重复传入的参数(以后不必每次都传)。例如,事件浏览器添加事件的辅助方法:

关于性能:

  • 存取arguments对象通常要比存取命名参数要慢一点.
  • 一些老版本的浏览器在arguments.length的实现上是相当慢的.
  • 创建大量嵌套作用域和闭包函数会带来花销,无论是在内存还是速度上.

参考资料