首頁 > 軟體

js中閉包結合遞迴等於柯里化原理解析

2022-07-08 14:06:36

引言

我們不妨以兩數相加為例子,遞進說明。

我們通常是這樣寫一個函數來求得 兩數相加 的值:

function sum(a,b){
    console.log(a+b)
}
sum(1,2)

這樣寫一點毛病沒有!

不過呢?問題總會在發展中產生,產品經理又要加一個值,需求:三數相加;

咱通常來說,第一時間,就在原基礎上,直接再加一個引數就是了;

於是,修改後像是這樣:

function sum(a,b,c){
    console.log(a+b+c)
}
sum(1,2,3)

問:這樣寫,有毛病嗎??

答:太有毛病了!

這樣一改,既違反了:“開閉原則”、又違反了:“單一職責原則”。

為不太熟悉設計原則的小夥伴們,簡單解釋下:

  • 什麼是“開閉原則”?即:我們程式設計中要儘可能的避免直接修改函數、類或模組,而是要在原有基礎上拓展它;
  • 什麼是“單一職責原則”?即:每個函數、類或模組,應該只負責一個單一的功能;

首先,咱修改了 sum 函數的傳參以及內部的呼叫 ⇒ 則違反“開閉原則”

其次,sum 函數本來只負責兩數相加,修改後,它又負責三數相加,職責已經發生了變化 ⇒ 則違反 “單一職責原則”;

如果正規按照單一責任來寫,應該是:

// 負責兩數相加
function sum2(a,b){
    console.log(a+b)
}
// 負責三數相加
function sum3(a,b,c){
    console.log(a+b+c)
}

事實上,是不可能這樣去寫的,因為如果有一萬個數相加,得寫一萬個函數。

而 加法只有一個!! 不管你最終要加幾個值,總是要一個加一個。

於是乎,我們設想,能不能寫一個這樣的函數:它的功能,就是“加”,引數跟幾個,我就加幾個。

// 負責「加法」,
function addCurry(){
    ...
    ...
    ...
}
addCurry(1)(2) // 兩數相加
addCurry(1)(2)(3) // 三數相加
...
addCurry(1)(2)(3)...(n) // n 數相加

沒錯,這個函數就是:柯里化!!(或者說這個過程叫柯里化,這個思想叫柯里化,本瓜認為這裡不需要太死扣定義)

接著,我們一步步來試試,它會是怎樣構成的?

為了能夠實現一個加一個,即儲存引數的目的,我們想一想,還有什麼法寶?

沒錯,JS 奧義:閉包!

其實,本瓜時常想,閉包的終極祕密是什麼?最後將其理解為 4 個金光閃閃的大字:延遲處理!

什麼意思?簡單解釋下:

function directHandle(a,b){
    console.log("直接處理",a,b)
}
directHandle(111,222)
// 直接處理 111 222
function delayHandle(a){
    return function(b){
         console.log("延遲處理",a,b)
    }
}
delayHandle(111)
// ƒ (b){
//	    console.log("延遲處理",a,b)
//	}

如上 delayHandle(111) 不像 directHandle(111,222) 直接列印值,而是先返回一個函數 f(b);111 也被臨時儲存了,delayHandle(111)(222),則得到相同的輸出。這就是:延遲處理的思想。

另外補一句:

延遲處理是函數語言程式設計的精華所在,在不能保證每個函數都是純函數的前提下,在管道處理的最後,再進行處理,能最大程度的保證減少副作用。也就是 Monad 思想,此處不做展開。

言歸正傳,於是乎,我們借用閉包來實現最初版的柯里化:

// 兩數相加
function addCurry(a){
    return function(b){
            console.log(a+b)
    }
}
addCurry(1)(2)
// 三數相加
function addCurry(a){
    return function(b){
        return function(c){
             console.log(a+b+c)
        }
    }
}
addCurry(1)(2)(3)

寫兩個閉包的過程,聰明的你一定就明白了,這樣一直寫下去,不就是遞迴嗎?!

於是乎,我們知道,當引數是 n 個的時候,需要遞迴 n-1 次 return function

於是乎,addCurry 寫法如下:

 let arr = []
 function addCurry() {
     let arg = Array.prototype.slice.call(arguments); // 遞迴獲取後續引數
     arr = arr.concat(arg);
      if (arg.length === 0) { // 如果引數為空,則判斷遞迴結束
          return arr.reduce((a,b)=>{return a+b}) // 求和
      } else {
          return addCurry;
      }
  }
addCurry(1)(2)(3)()

OK,至此,,大功告成!!

以上,用最簡單的程式碼解釋了 —— 為什麼我說:柯里化 == 閉包+遞迴 ?

柯里化是一種思想,上面的 addCurry 可以說是最簡單的一種實踐。在函數語言程式設計中,Curry 更是大放異彩,比如 compose(fn1)(fn2)(fn3)…(fnN)(args) 等等。

如果以後有人再問你柯里化,可以往這個方向上答。。。

以上就是閉包結合遞迴等於柯里化原理解析的詳細內容,更多關於閉包結合遞迴等於柯里化的資料請關注it145.com其它相關文章!


IT145.com E-mail:sddin#qq.com