首先,柯里化(Currying)是什麼呢?git
簡單說,假若有一個函數,接受多個參數,那麼通常來講就是一次性傳入全部參數並執行。
而對其執行柯里化後,就變成了能夠分屢次接收參數。github
如今有一個加法函數:數組
function add(x, y, z) {
return x + y + z
}
複製代碼
調用方式是 add(1, 2, 3)
。閉包
若是執行柯里化,變成了 curriedAdd()
,從效果來講,大體就是變成 curriedAdd(1)(2)(3)
這樣子。函數
如今先不看怎麼對原函數執行柯里化,而是根據這個調用方式從新寫一個函數。
代碼多是這樣的:post
function curriedAdd1(x) {
return function (y) {
return function (z) {
return x + y + z
}
}
}
複製代碼
假如如今想要升級一下,不止能夠接受三個參數。
可使用 arguments
,或者使用展開運算符來處理傳入的參數。ui
可是有一個衍生的問題。由於以前每次只能傳遞一個,總共只能傳遞三個,才保證了調用三次以後參數個數恰好足夠,函數才能執行。spa
既然咱們打算修改成能夠接受任意個數的參數,那麼就要規定一個終點。好比說,能夠規定爲當再也不傳入參數的時候,就執行函數。prototype
下面是使用 arguments
的實現。code
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)()
複製代碼
這時能夠發現,上面的整個函數裏,與函數具體功能(在這裏就是執行加法)有關的,就只是當沒有傳遞參數時的部分,其餘部分都是在實現怎樣屢次接收參數。
那麼,只要讓 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)())
複製代碼
上面的代碼裏,對於函數執行時機的判斷,是根據是否有參數傳入。
可是更多時候,更合理的依據是原函數能夠接受的參數的總數。
函數名的 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 的 curry 方法》,這裏就不分析了。