一道面试题让我彻底明白柯里化

1,098 阅读4分钟

本文正在参加「金石计划」

什么是柯里化

在计算机科学中,柯里化(Currying)是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数且返回结果的新函数的技术。

Currying 为实现多参函数提供了一个递归降解的实现思路——把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数而且返回结果的新函数

什么是函数的柯里化?

通俗来讲:函数柯里化,又可以被称作部分求值。此类函数,在传入部分的值后,并不会立刻求值,而是返回一个函数,并且在参数未满足条件时(参数个数未达标或未传入结束标识符),会将传入的值存储在形成的闭包中,等待下一次调用。

应用场景

function add(a, b, c) {
    return a + b + c
  }
  var fn = curry(add)
  fn(1, 2, 3)
  fn(1, 2)(3)
  fn(1)(2)(3)
  fn(1)(2, 3)

如上图, 给定一个add函数接受三个参数,我们要如何实现将多个参数逐次的传递给add函数,且当函数参数不足时add 函数不会有返回值。

优雅的解决方案

function curry(fn) {
    var length = fn.length  // 函数接收参数的个数
    let args = [].slice.call(arguments, 1); // 拿到处函数以外的参数
    return function() { //返回一个参数
      let _args = args.concat([...arguments]) // 合并参数 
      if(_args.length < length) { // 判断参数数量是否足够
        return curry.call(this, fn, ..._args) // 递归
      }else { // 执行函数
        return fn.apply(this, _args)
      }
  }
}

效果图:

图片.png

如上图, 我们可以轻松的实现函数的柯里化调用, 同时我们也发现实现柯里化需要借助闭包的思想不断的维护一个承装参数的数组,当数组的长度与函数接收参数的个数一致时,我们就执行函数,否则递归自身。

使用场景

那函数柯里化有什么应用场景呢?我们在什么条件下需要将函数柯里化呢?

我认为函数的柯里化有如下三点使用场景:

  • 闭包函数的高阶应用
  • 作用就是通过延长变量的生命周期, 来吧一个函数传递多个参数完成的事情
  • 改变成一个函数传递一个参数, 多个函数的形式

特殊的柯里化函数(偏函数)

什么是偏函数?

简单的理解偏函数,它是对原始函数的二次封装,是将现有函数的部分参数预先绑定为指定值,从而得到一个新的函数,该函数就称为偏函数。相比原函数,偏函数具有较少的可变参数,从而降低了函数调用的难度。

应用场景

function add(a, b, c) {
    return a + b + c
  }
  var fn = partial(add)  // 实现第一次只接受一个参数的偏函数
  fn(1)(2)(3)
  fn(1)(2, 3)

如上图,如果我们要实现第一次只接受一个参数的偏函数,我们的partial函数该怎么写呢?

  function partial(fn) {
    let count = arguments[1] || 0
    var length = fn.length
    let args = count === 0 ? [0] : [].slice.call(arguments, 1);
    return function() {
      args.push(...arguments)
      args[0] = args[0] + 1
      let _args = args
      if(_args.length-1 < length) {
        return partial.call(this, fn, ..._args)
      }else {
        return fn.apply(this, _args.slice(1))
      }
  }
}

图片.png 如上图,我们巧妙的维护了一个 count, 用count来记录它的递归次数,如果是count为 0, 说明为第一次调用, 我们则不接受 fn 后面的参数。

二者区别

  • 柯里化是将一个多参数函数转换成多个单参数函数,也就是将一个 n 元函数转换成 n 个一元函数。
  • 偏函数则是固定一个函数的一个或者多个参数,也就是将一个 n 元函数转换成一个 n - x 元函数。

优缺点

既然我们已经会封装和使用函数的柯里化了,那我再来介绍介绍它的优缺点吧。

优点:

  • 延迟计算,当参数未齐或者未传入结束标识符时,仍返回函数
  • 参数复用,利用闭包的特性,将第一个传入的参数存入闭包空间,不会被销毁。(私有化)

缺点:

  • 容易造成内存泄漏,因为是利用闭包解决的
  • 性能不高,因为使用了递归的方式
  • 函数进行了嵌套,不易理解

参考链接

大佬,JavaScript 柯里化,了解一下?