函数柯里化

Curry函数

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

什么是函数柯里化

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

柯里化函数的作用

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

柯里化的实现依赖

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

函数柯里化的特点

柯里化(Currying)具有的特点:

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

关于性能:

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

函数柯里化案例

基础的Curry

function add(a, b) {
  // 局部应用
  if (typeof a === 'undefined') {
    return add
  }
  else if (typeof b === 'undefined') {
    return function (newB) {
      return a + newB // 此处a的值因闭包而保存
    }
  }
  // 完全应用
  return a + b
}
add() // add函数
var newAdd = add(1) // 局部应用了参数1的函数
newAdd(3) // 4
add(1, 4) // 5 完全应用

通用化的Curry

function currying(fn, ..._args) {
  return function (args) {
    return fn.apply(null, _args.concat(...args))
  }
}

const newAdd = currying(function (a, b) {
  return a + b
}, 1)

console.log(newAdd(5)) // 6

增强通用化的Curry

  1. 通过JS对原始数据的转换规则
function currying(func) {
  /**
   * 闭包原理
   * 通过返回函数索引闭包内变量
   * 将_args保存在闭包(_adder)内,不被回收机制回收,从而反复收集参数.
   * */
  let _args = []
  return function adder(...args) {
    // 无新参数返回旧值,否则计算新值
    if (args.length) {
      // 将所有新参数添加至私有变量中
      _args.push(...args)
      // valueOf 权限大于 toString,它能将数值正确反映到函数上
      adder.valueOf = adder.toString = () => func(_args)
    }
    return adder
  }
}

// 将所有函数调用的值都追加到 currying 函数的一个闭包变量中,最后统一处理
const add = currying(arr => arr.reduce((a, b) => a + b))

console.log(add(1, 2)(2)(5, 5)(5) + 1) // 21

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

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

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

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

参考资料