[JavaScript] call 與 bind 雖然相似 卻只有bind 可以做到 Currying ,那麼問題來了,什麼是 Currying ?
Function.prototype.bind 提供的好處,不只是為了一個函數做一個綁定 this 範疇的包裹,其最廣為人知的做法是能將一個函數做「科里化」,這篇文章會從 bind 與 call 相似的語法格式說起,並且討論科里化的好處。
此篇接續上一篇的 [JavaScript] 聊聊call、apply、bind的差異與相似之處 ,我們可以明白 call 、 apply 、 bind 三者的差異性以及相似程度,但針對 bind 我們僅僅只討論第一個參數能做到的事,這一篇文章想做的 — 詳細說明 bind 語法格式,進而討論 bind 的更多樣化用法,以及 Functional Programming 的傳教僧常說的Currying。
然而 Currying 跟 bind 有絕對的關係嗎?我們先留個伏筆。
我們先來看看 call 與 bind 的語法格式
call 的語法格式
fun.call(thisArg[, arg1[, arg2[, ...]]])
bind 的語法格式
fun.bind(thisArg[, arg1[, arg2[, ...]]])
可以發現說 call 與 bind 的語法格式幾乎一樣。
而其根本差異性是 call 會直接去執行函數並回傳結果,而 bind 回傳的是一個「被設定好的函數」,這也意味著 bind 不只能做到 wrapper 特定 this ,還能去 wrapper 參數。
而這個「設定」兩字正是 bind 的精髓所在。
這裡整理兩篇以來提到的bind用法,皆是從這句話所衍生
- bind 可以「設定」 this
- bind 可以「設定」參數
透過第二點的好處我們可以輕鬆做到簡單的 Currying 。
實現簡單的currying
Currying — 科里化,到底是要科里什麼東西
先定義科里 再來討論科里,我們來看看維基百科的定義:
在計算機科學中,柯里化(英語:Currying),又譯為卡瑞化或加里化,是把接受多個參數的函數變換成接受一個單一參數(最初函數的第一個參數)的函數,並且返回接受餘下的參數而且返回結果的新函數的技術。
語畢,你發現其實上面做的事很明顯的在完成 currying 這個動作,用一個口語化的方式來解釋,科里化想做的只是把函數的接口做封裝,也就是上面說的將其他參數先做設定,接口單純。
然而用bind去做curry還是會有它的限制所在,舉例:我們沒有辦法把特定index的參數給bind進去。
f(x,y,z) →無法針對y變數去設定
有一個很簡單,小孩子也寫得出來的solution,就是重新封裝一個新的函數。
Arrow Function — 箭頭函數的救贖
從上述的例子可以看到,這類的函數,就如同高中數學所學的函式推演,讓我們來看看最簡單的函數的題目,並想象怎麼「題意」化為程式語言。
題目:當 f(x,y,z) = 2x + y + 3z,
如果 g(x,z) = f(x,3,z); 請問 g(2,3) =?
g(2,3) = 2*2 +3 +3*3 = 16 #
箭頭函數可以直接忽略數學運算上不在乎的「動態this」,透過數學化的方式撰寫函數,這樣子的程式風格如同仰賴數學題意說明,無論的定義或是應用,以及回傳結果。
透過不停的封裝,我們可以參數預設定,實現「科里化」。
來下一個 conclusion 吧,從這篇廢文可以知道一件事。
- bind 方法不只可以綁定 this 其接口與 call 方法一樣
- bind 方法可以設定函數,於是可以實作 Currying
- Currying 不需透過 bind 去實現
- 箭頭函數能讓你忽略動態 this 實現如同數學運算時的 Coding Style
另外
- bind 會控制 this 很不方便
- bind 無法預設定特定參數 很不方便
寫在最後
因為有太多文章把 bind 跟 curry 放在一起做討論,本篇標題也故意透過這種方式下題。
看到最後你會發現 — 其實 Currying 就在身邊,不要想得太複雜。