柯里化、偏函數、反柯里化

柯里化

  • 描述

柯里化算是特殊的偏函數,把一個多參數函數轉換成多個單參數函數,也就是說把一個具備n個參數的函數轉換成n個一元函數
  • 示例

// 正常寫法
function add (a, b) {
  return a + b
}
const resAdd = add(2, 3)
console.log(resAdd)  // 5
// 柯里化
function currieAdd (a) {
  return function (b) {
    return a + b
  }
}
const resCurrie = currieAdd(2)(3)
console.log(resCurrie)  // 5
  • 通用寫法

上面的示例代碼比較簡單,若是有十幾個參數呢?因此須要一個通用柯里化的寫發
  • 代碼的關鍵點在於javascript

    • 閉包,調用柯里化函數(currie)返回另一個函數(_myFn),經過閉包緩存真正執行運算的函數(fn)和參數(args)
    • 經過返回的函數傳遞參數,並進行判斷,若是參數已經傳遞夠了,就執行函數(fn)並返回結果,若是參數還沒傳遞完,則繼續返回函數(_myFn)接收參數
// 柯里化一個函數
function currie (fn) {
  // 利用閉包緩存傳遞進來的參數
  const args = []
  return function _myFn (arg) {
    args.push(arg)
    if (args.length === fn.length) {
      // 說明參數已經傳遞夠了,執行fn函數並返回結果
      return fn.apply(null, args)
    } else {
      // 發現參數沒有傳遞完,則返回_myfn函數,繼續調用
      return _myFn
    }
  }
}

// 示例 1
function add_1 (a, b) {
  return a + b
}
const currieAdd_1 = currie(add_1)
const res1 = currieAdd_1(2)(3)
console.log(res1)  // 5

// 示例 2
function add_2 (a, b, c, d, e) {
  return a + b + c + d + e
}
const currieAdd_2 = currie(add_2)
const res2 = currieAdd_2(1)(2)(3)(4)(5)
console.log(res2)  // 15

偏函數

  • 描述

偏函數又叫局部應用,固定函數的一個或多個參數,也就是說把一個 n元函數轉換成一個 n - x元函數
  • 示例

// 封裝一個ajax方法
function ajax (url, data, callback) {
  ...
}
// 調用ajax方法,
ajax('http://lyn.com', { a: 'aa' }, function () { // 回調 A })
ajax('http://lyn.com', { b: 'bb' }, function () { // 回調 B })
...
ajax('http://lyn.com', { y: 'yy' }, function () { // 回調 Y })
發現以上全部的調用,第一個參數都同樣,這時候就須要想有沒有什麼方法能夠簡化這種重複參數的填寫, 偏函數出馬
// 偏函數
function partial (url) {
  return function (data, cb) {
    ajax(url, data, cb)
  }
}

// 調用偏函數
const partialAjax = partial('http://lyn.com')

// 發送ajax請求
partialAjax({ a: 'aa' }, function () { // 回調 A })
partialAjax({ b: 'bb' }, function () { // 回調 B })
...
partialAjax({ y: 'yy' }, function () { // 回調 Y })
  • 通用寫法

代碼的關鍵點java

偏函數的代碼比較簡單,就是利用閉包緩存實際的執行方法(fn)和與之的參數(preset),而後返回一個接收剩餘參數的方法,方法的實現就是執行fn並返回結果
function partial (fn, ...preset) {
  return function (...args) {
    return fn.apply(null, preset.concat(args))
  }
}

// 示例, 經過一個簡單的add方法來模擬
function add (a, b, c, d) {
  return a + b + c + d
}
// 屢次調用傳遞的前兩個參數是同樣的
// add(1, 2, 3, 4)
// add(1, 2, 5, 6)
const partialAdd = partial(add, 1, 2)
const res1 = partialAdd(3, 4)
console.log(res1)  // 10
const res2 = partialAdd(5, 6)
console.log(res2)  // 14

反柯里化

  • 說明

柯里化其實就是偏函數的特殊狀況,因此在反柯里化這裏就之說偏函數,我以爲這樣更合適
  • 對比 —— 偏函數、反柯里化

  • 偏函數:偏函數是對高階函數的降階處理,再樸素點的描述就是,下降函數的通用性,建立一個針對性更強的函數,好比上面講的偏函數部分的ajaxpartialAjax
  • 反柯里化:和偏函數恰好相反,增長方法的適用範圍(即通用性)
  • 通用代碼

Function.prototype.uncurrie = function (obj) {
  // 參數obj是須要操做的對象
  // 這裏的this是指obj對象須要借用的方法,好比示例中的Array.prototype.push
  const fnObj = this
  return function (...args) {
    // 難點,如下代碼至關於:fnObj.call(obj, ...args), 沒理解請看下面的 「代碼解析」 部分
    return Function.prototype.call.apply(fnObj, [obj, ...args])
  }
}

// 示例,導出Array.prototype.push方法給對象使用
const obj = { a: 'aa' }
const push = Array.prototype.push.uncurrie(obj)
push('b')
push('c')
console.log(obj)  // {0: "b", 1: "c", a: "aa", length: 2}
  • 代碼解析

這部份內容負責解析上面的通用代碼ajax

  • 首先聲明,我的以爲這個通用代碼是不必的,由於這段通用代碼的本質就是call、apply,經過call、apply改變方法的this上下文,使得對象可使用不屬於它的方法,這也是反柯里化的本質,加強方法的使用範圍
  • 這段通用代碼的難點在於Function.prototype.call.apply(fnObj, [obj, ...args])這句,如下解析採用通用代碼中的示例代碼
  • 如下解釋須要你熟悉apply、call方法的源碼實現,若是不熟悉請參考 javascript源碼解析,裏面的call、apply兩部分的源碼解析會回答你的疑問
  • 正式開始解析通用代碼,經過通用代碼中的示例代碼進行講解
  • 通用代碼其實就是個閉包,執行Array.prototype.push.uncurrie(obj),傳遞一個須要操做的對象(const obj = {a: 'aa'}),其中fnObj = Array.prototype.push,這時向外面return一個接收參數的函數
  • 返回的函數中就一句代碼:

return Function.prototype.call.apply(fnObj, [obj, ...args])緩存

  • 上面的代碼能夠翻譯爲:

return Function.prototype.call.apply(Array.prototype.push, [{a: 'aa'}, ...args])閉包

  • 再進一步翻譯(須要瞭解call、apply的原理,不明白請參考javascript源碼解析):

return Array.prototype.push.call({a: 'aa'}, ...args),這句就等同於:
Arrray.prototype.push.call(obj, 'b'),看到這裏就會明白我開始說的 「聲明」 部分的意思了app

總結

柯里化其實就是特殊的偏函數,偏函數的本質就是經過調用函數,預置一部分參數,而後返回一個參數更少但針對性更強的函數;而反柯里化,不知道爲啥叫反柯里化,感受應該叫反偏函數更好一點,反柯里化做用和偏函數相反,它的本質是加強一個函數的使用範圍,讓一個對象可使用不屬於對象本身的方法,就像apply、call、bind(也有偏函數的做用)的做用,而事實上反柯里化就是經過apply、call方法實現的
  • 偏函數都用在哪些地方

    • 須要減小參數的地方
    • 須要延遲計算的地方
    • Function.prototype.bind其實就是偏函數的應用
  • 反柯里化都用在哪些地方

    • 一個對象須要借用其它對象的方法時用反柯里化
相關文章
相關標籤/搜索