簡介
首先,柯里化(Currying)是什麼呢?
簡單說,假如有一個函式,接受多個引數,那麼一般來說就是一次性傳入所有引數並執行。
而對其執行柯里化後,就變成了可以分多次接收引數。
實現
階段1
現在有一個加法函式:
function add(x, y, z) {
return x + y + z
}
複製程式碼
呼叫方式是 add(1, 2, 3)
。
如果執行柯里化,變成了 curriedAdd()
,從效果來說,大致就是變成 curriedAdd(1)(2)(3)
這樣子。
現在先不看怎麼對原函式執行柯里化,而是根據這個呼叫方式重新寫一個函式。
程式碼可能是這樣的:
function curriedAdd1(x) {
return function (y) {
return function (z) {
return x + y + z
}
}
}
複製程式碼
階段2
假如現在想要升級一下,不止可以接受三個引數。
可以使用 arguments
,或者使用展開運算子來處理傳入的引數。
但是有一個衍生的問題。因為之前每次只能傳遞一個,總共只能傳遞三個,才保證了呼叫三次之後引數個數剛好足夠,函式才能執行。
既然我們打算修改為可以接受任意個數的引數,那麼就要規定一個終點。比如說,可以規定為當不再傳入引數的時候,就執行函式。
下面是使用 arguments
的實現。
function getCurriedAdd() {
// 在外部維護一個陣列儲存傳遞的變數
let args_arr = []
// 返回一個閉包
let closure = function () {
// 本次呼叫傳入的引數
let args = Array.prototype.slice.call(arguments)
// 如果傳進了新的引數
if (args.length > 0) {
// 儲存引數
args_arr = args_arr.concat(args)
// 再次返回閉包,等待下次呼叫
// 也可以 return arguments.callee
return closure
}
// 沒有傳遞引數,執行累加
return args_arr.reduce((total, current) => total + current)
}
return closure
}
curriedAdd = getCurriedAdd()
curriedAdd(1)(2)(3)(4)()
複製程式碼
階段3
這時可以發現,上面的整個函式裡,與函式具體功能(在這裡就是執行加法)有關的,就只是當沒有傳遞引數時的部分,其他部分都是在實現怎樣多次接收引數。
那麼,只要讓 getCurriedAdd
接受一個函式作為引數,把沒有傳遞引數時的那一行程式碼替換一下,就可以實現一個通用的柯里化函式了。
把上面的修改一下,實現一個通用柯里化函式,並把一個階乘函式柯里化:
function currying(fn) {
let args_arr = []
let closure = function (...args) {
if (args.length > 0) {
args_arr = args_arr.concat(args)
return closure
}
// 沒有新的引數,執行函式
return fn(...args_arr)
}
return closure
}
function multiply(...args) {
return args.reduce((total, current) => total * current)
}
curriedMultiply = currying(multiply)
console.log(curriedMultiply(2)(3, 4)())
複製程式碼
階段4
上面的程式碼裡,對於函式執行時機的判斷,是根據是否有引數傳入。
但是更多時候,更合理的依據是原函式可以接受的引數的總數。
函式名的 length
屬性就是該函式接受的引數個數。比如:
function test1(a, b) {}
function test2(...args){}
console.log(test1.length) // 2
console.log(test2.length) // 0
複製程式碼
改寫一下:
function currying(fn) {
let args_arr = [], max_length = fn.length
let closure = function (...args) {
// 先把引數加進去
args_arr = args_arr.concat(args)
// 如果引數沒滿,返回閉包等待下一次呼叫
if (args_arr.length < max_length) return closure
// 傳遞完成,執行
return fn(...args_arr)
}
return closure
}
function add(x, y, z) {
return x + y + z
}
curriedAdd = currying(add)
console.log(curriedAdd(1, 2)(3))
複製程式碼
Lodash 中的柯里化
Lodash 中的柯里化就靈活得多了,可以先放置佔位符之後再傳值。
可以參考一下《實現 lodash 的 curry 方法》,這裡就不分析了。