what's currying
var add = function(a,b){
return a + b
}
add(2,3) //5
curry化就是把add(2,3)變成add(2)(3)
var add = function(a){
return function(b){
return a + b;
}
}
add(2)(3) //5
我們仔細看看這里發(fā)生了什么
var add2 = add(2)
console.log(add2) //[Function]
add2(3) //5
add2(5) //7
我們注意到add(2)返回的是一個函數(shù)拷获,這個函數(shù)接受一個參數(shù)并將其加二后返回。
currying就像一種預加載技術台猴。很多時候我們調(diào)用一個函數(shù)需要多個參數(shù),但是在某個階段你只知道其中的一部分參數(shù)咐柜,不要緊澳淑,你先傳入已知的參數(shù)屁擅,并返回新的函數(shù)犁罩,等所有的參數(shù)都被接受的時候齐蔽,再真正的調(diào)用函數(shù)。
why currying
currying的好處有很多床估,很明顯的一點是含滴,我們可以用currying來寫出更加友好的函數(shù)。很多時候丐巫,我們可能不知道函數(shù)調(diào)用的所有參數(shù)谈况,這個時候currying就非常有用!
下面我們來寫一個可以縮放盒子并給盒子涂顏色的函數(shù)
var scale = function(val, stuff){
stuff.size *= val
}
var paint = function(val, stuff){
stuff.color = val
}
var ScaleAndPaint = function(s, color, stuff){
scale(s, stuff)
paint(color, stuff)
}
我們來測試一下這個函數(shù)
var box = {
size: 10,
color: 'green'
}
ScaleAndPaint(2,'black',box)
console.log(box) //{ size: 20, color: 'black' }
works great! 但是這時候我們發(fā)現(xiàn)递胧,幾乎所有的盒子都只需要變成兩倍就可以了碑韵,可以我們卻需要每一次都傳入2,這實在太不友好了缎脾。下面我們來currying化我們的函數(shù)
var ScaleAndPaint = function(s){
return function(color){
return function(stuff){
scale(s, stuff)
paint(color, stuff)
}
}
}
不是都變成兩倍大嗎祝闻,簡單
var DoubleAndPaint = ScaleAndPaint(2)
再進一步
var DoubleAndBlue = DoubleAndPaint('blue')
var DoubleAndRed = DoubleAndPaint('Red')
DoubleAndBlue(box) //{ size: 40, color: 'blue' }
lodash
雖然currying化的函數(shù)用起來真的很舒服,但是我們注意到遗菠,它寫起來可比原來的那種麻煩多了治筒,需要多次的嵌套返回函數(shù)屉栓。另一個問題是在學會currying之前舷蒲,我們已經(jīng)寫好了我們的函數(shù)耸袜,現(xiàn)在我們想把它curry化,咋辦牲平?
幸運的是堤框,我們有很多框架實現(xiàn)了將普通函數(shù)currying化的api,比如lodash和ramda纵柿,使用起來不要太簡單啊
var curry = require('lodash').curry;
var ScaleAndPaint = curry(function(s, color, stuff){
scale(s, stuff)
paint(color, stuff)
})
ScaleAndPaint(2)('black')(box)
works great!
自己實現(xiàn)蜈抓!
var slice = Array.prototype.slice;
var toArray = function(a){ return slice.call(a) }
//組合參數(shù)列表,因為arguments其實并非真正的數(shù)組昂儒,所以其沒有concat方法
var concatArgs = function(args1, args2){
return args1.concat(toArray(args2));
}
//如果超出原函數(shù)的參數(shù)列表長度沟使,那么只取前幾個
var trimArrLength = function(arr, length){
if ( arr.length > length ) return arr.slice(0, length);
else return arr;
}
//為了簡單起見,假設參數(shù)總數(shù)不會超過6
var createFn = function(fn, args, totalArity){
var remainingArity = totalArity - args.length;
switch(remainingArity){
case 0: return function(){ return processInvocation(fn, concatArgs(args, arguments), totalArity) };
case 1: return function(a){ return processInvocation(fn, concatArgs(args, arguments), totalArity) };
case 2: return function(a,b){ return processInvocation(fn, concatArgs(args, arguments), totalArity) };
case 3: return function(a,b,c){ return processInvocation(fn, concatArgs(args, arguments), totalArity) };
case 4: return function(a,b,c,d){ return processInvocation(fn, concatArgs(args, arguments), totalArity) };
case 5: return function(a,b,c,d,e){ return processInvocation(fn, concatArgs(args, arguments), totalArity) };
case 6: return function(a,b,c,d,e,f){ return processInvocation(fn, concatArgs(args, arguments), totalArity) };
}
}
var processInvocation = function(fn, argsArr, totalArity){
argsArr = trimArrLength(argsArr, totalArity);
if ( argsArr.length === totalArity ) return fn.apply(null, argsArr);
return createFn(fn, argsArr, totalArity);
}
var curry = function(fn){
return createFn(fn, [], fn.length)
}
測試一下
var add = curry(function(a,b,c,d,e){return a+b+c+d+e})
console.log(add(1,2)(3,4)(5)) //15
works great!