博客 / 詳情

返回

函數式編程之柯里化

curry柯里化

首先我們先來看一個問題,如果實現一個add函數,可實現下面的功能

add(1,2,3) // 6
add(1)(2)(3) // 6
add(1,2)(3) // 6
add(1,2,3,4)(5)(6,7) // 28

當然了,所需要用到的知識點便是柯里化。

首先看下柯里化定義:
用於緩存函數參數的一種方式;給函數分步傳遞參數,每次傳遞部分參數,並返回一個更具體的函數接收剩下的參數,這中間可嵌套多層這樣的接收部分參數的函數,直至返回最後結果。

初看上面這行字,可能不太好理解,我大概總結了下,如何去實現或者説去理解柯里化,大致是下面兩點。

1、如何進行參數的緩存
2、在何時執行原函數
當收集到所需要的參數時,便去執行目標函數,得到結果,否則繼續收集參數。

這裏就不展開討論閉包、call、apply、bind這些概念了。

舉個例子

function add(a,b,c) {
  console.info(a, b, c, '==此函數需要傳遞三個參數執行==')
  return a + b + c
}

不難看出上面的函數執行需要三個參數,因此我們在調用test時,需要傳遞a,b,c否則函數執行異常;這時候回到上面的兩點,如何進行參數的緩存以及何時執行原函數去得到目標結果。

接下來看看如何去實現

固定參數個數的柯里化

/**
 * 傳入固定參數長度的柯里化
 * @params fn 原函數
 * @params  args 初始化參數
 */
function curry(fn, ...args) {
  const len = fn.length
  return function() {
    // 參數收集
    const allArgs = [...args, ...arguments]
    // 達到目標參數個數後,執行原函數
    if (allArgs.length >= len) {
      return fn.apply(null, allArgs)
    }
    return curry.call(null, fn, ...allArgs)
  }
}

function add(a,b,c) {
  return [...arguments].reduce((pre, cur) => pre += cur, 0)
}

let test = curry(add)

console.info(test(1,2,3)) // 6
console.info(test(1,2)(3)) // 6
console.info(test(1)(2)(3)) // 6

test = null // 防止內存泄漏

參數不固定

1、當傳入空的參數時,執行原函數
/**
 * 傳入參數不固定,參數為空時執行函數的柯里化
 * @params fn 原函數
 * @params  args 初始化參數
 */
function _curry(fn, ...args) {
  let allArgs = [...args]
  return function next(..._args) {
    allArgs = [...allArgs, ..._args]
    if (_args.length === 0) {
      return fn.apply(null, allArgs)
    }
    return next
  }
}

function _add() {
  return [...arguments].reduce((pre, cur) => pre += cur, 0)
}

const _test = _curry(_add)

_test(1,2)
_test(3)
console.info(_test()) // 6

_test(4)
console.info(_test()) // 10

_test = null // 防止內存泄漏
2、直接返回

這裏需要用到一個小知識點,在解析函數的原始值時,會用到函數的隱式轉換toString,因此我們的執行原函數節點在於toString

/**
 * 傳入參數不固定柯里化
 * @params fn 原函數
 * @params  args 初始化參數
 */
function _curry(fn, ...args) {
  let allArgs = [...args]
  function next(..._args) {
    allArgs = [...allArgs, ..._args]
    return  _curry.call(null, fn, ...allArgs)
  }
  next.toString = function() {
    return fn.apply(null, allArgs)
  }
  return next
}

function _add() {
  return [...arguments].reduce((pre, cur) => pre += cur, 0)
}

const _test = _curry(_add)

_test(1, 2) // 2

_test(4)(5)(6) // 15
console.info(_test()) // 10

_test = null // 防止內存泄漏

上述便是柯里化用於延遲執行的幾種用法

參數複用

能夠理解上述用法後,柯里化用於參數複用就輕而易舉了,如下例

/**
 * Object.prototype.toString.call(obj) === '[object ***]'
 */
function typeOf(value) {
  return function(obj) {
    const toString = Object.prototype.toString
    const map = {
      '[object Boolean]': 'boolean',
      '[object String]': 'string',
      '[object Number]': 'number',
      '[object Function]': 'function',
      '[object Array]': 'array',
      '[object Date]': 'date',
      '[object RegExp]': 'regExp',
      '[object Undefined]': 'undefined',
      '[object Null]': 'null',
      '[object Object]': 'object'
    }
    return map[toString.call(obj)] === value
  }
}

const isNumber = typeOf('number')
const isString = typeOf('string')
isNumber(0) // true
isString('a') // true
user avatar
0 位用戶收藏了這個故事!

發佈 評論

Some HTML is okay.