在前面一篇文章我們提到:
柯裡化可以通過閉包+遞歸實作
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)() // 6
這樣寫,沒什麼毛病,通過:
Array.prototype.slice.call(arguments)
收集了參數,推到
arr
數組裡;當存在參數時,遞歸調用,當沒有參數的時候,再用
reduce()
求和,傳回;
但是這樣寫,會有外部變量 arr,arr 不清理的話,會被一直儲存,導緻連續調用出錯:
addCurry(1)(2)(3)() // 6
addCurry(1)() // 7
是以,需要改寫成這樣:
function addCurry() {
let arr = [...arguments]
let fn = function () {
if(arguments.length === 0) {
return arr.reduce((a, b) => a + b)
} else {
arr.push(...arguments)
return fn
}
}
return fn
}
把 arr 再加一層封裝到函數裡,每次調用,都會重新聲明,原值會被清理;
之前相加的思路仍然不變;
但這樣做,還有一個問題:總是需要用空括号 () 為結尾,來判斷結束調用;
能不能去掉這個,直接如下這樣就能求值:
addCurry(1)(2)(3)
//或
addCurry(1)(2,3)
答案是可以的,原理是用到
toString
,當用 Function 的值做計算的時候,會調用 toString 做隐式轉換;
let fn = function(){}
fn.toString = () => 1
fn == 1 // true
是以,我們的代碼更新為:
function addCurry() {
let arr = [...arguments]
// 利用閉包的特性收集所有參數值
var fn = function() {
arr.push(...arguments);
return fn;
};
// 利用 toString 隐式轉換
fn.toString = function () {
return arr.reduce(function (a, b) {
return a + b;
});
}
return fn;
}
addCurry(1)(2)(3) == 6 // true
這裡一定要注意,直接列印 addCurry(1)(2)(3) 是不能拿值的,隻有當它做隐式轉化的時候,才能計算得正确得值。
小結:
其實不管是用空括号 () 作“開始執行reduce相加”的判斷依據,還是用 toString 的隐式轉換做依據,總是要有一個标準,來告訴柯裡化函數:你可以執行了!
柯裡化精髓是參數收集,是延遲執行!