函数柯里化是一种将一个拥有多个参数的函数转化为一系列只使用一个参数的函数的技术。

# 用途

  • 延迟计算
  • 参数复用
  • 生成动态函数

函数柯里化会降低函数的灵活性,但会提高函数的适用性。

举个正则匹配替换的例子:

// 匹配 apple 并替换为 APPLE
replace(/apple/g, 'APPLE')
// 匹配 apple 并替换为 pear
replace(/apple/g, 'pear')
// 匹配 apple 并替换为 Test
replace(/apple/g, 'Test')

像上面的例子,replace 的第一个参数都得传。如果使用函数柯里化,则:

// 将 replace 函数柯里化
const curryedReplace = curry(replace)
replaceApple = curryedReplace(/apple/g)
// 匹配 apple 并替换为 APPLE
replaceApple('APPLE')
// 匹配 apple 并替换为 pear
replaceApple('pear')
// 匹配 apple 并替换为 Test
replaceApple('Test')

通过函数柯里化,将 replace 的灵活度降低,提高适用性,仅针对单个逻辑执行函数。

# 实现

函数柯里化的实现实际上是通过闭包将函数的参数保存起来,当保存的函数参数达到一定数量(一般指函数的行参数量)时,再执行函数。

// 写法一:递归调用 curry,将函数参数通过传递给 curry 函数进行保存
function curry(fn, args) {
  args = args || [] // 保存函数参数
  // 闭包
  return function () {
    // 获取当前函数参数
    const curArgs = [...arguments]
    // 获取所有的函数参数 = 之前保存函数参数 + 当前函数参数
    const allArgs = args.concat(curArgs)
    // 所有的函数参数数量少于待执行函数形参的数量,递归调用 curry 函数
    return allArgs.length < fn.length
      ? curry.call(this, fn, allArgs)
      : fn.apply(this, allArgs)
  }
}
// 写法二:只执行一次 curry,利用闭包数据会常驻内存的特性,将参数保存到 args 变量
function curry(fn, args) {
  args = args || []
  return function next() {
    args = [...args, ...arguments]
    if (args.length < fn.length) {
      return next
    } else {
      const result = fn.apply(this, args)
      args = [] // 闭包数据会常驻内存,如果不重置数据会对下次执行造成影响
      return result
    }
  }
}

const fn = curry(function (a, b, c) {
  return [a, b, c]
})

console.log('>>  :', fn('a')('b', 'c')) // >>  : [ 'a', 'b', 'c' ]
console.log('>>  :', fn('a', 'b')('c')) // >>  : [ 'a', 'b', 'c' ]
console.log('>>  :', fn('a')('b')('c')) // >>  : [ 'a', 'b', 'c' ]

# 拓展

实现add(1)(2)(3)(4)

function curry(fn, args) {
  args = args || []

  function next() {
    args = [...args, ...arguments]
    return next
  }

  // 隐式调用逻辑
  ['toString', 'valueOf'].forEach(item => {
    next[item] = function () {
      const result = fn.apply(this, args)
      args = []  // 闭包数据会常驻内存,如果不重置数据会对下次执行造成影响
     	return result
    }
  })

  return next
}

const add = currying(function () {
  return [...arguments].reduce((acc, cur) => {
    acc +=cur
    return acc
  }, 0)
})

console.log('>>  :', add(1)(2, 3)(4)(5) + 0) // >> : 15
console.log('>>  :', +add(1)(2, 3)(4)) // >> : 10

参考链接:

  • https://zhuanlan.zhihu.com/p/120735088
  • https://github.com/mqyqingfeng/Blog/issues/42