在說compose函式之前,我們先來看一道題目:
Your task is to write a higher order function for chaining together a list of unary functions. In other words, it should return a function that does a left fold on the given functions.
chained([a,b,c,d])(input)複製程式碼
Should yield the same result as
d(c(b(a(input))))複製程式碼
大致意思就是寫個函式 能將多個函式進行組合成一個函式,就是一元鏈式函式
思考
當時我想,要想實現一個這樣的函式,肯定是需要有一個遍歷的過程,一個函式的執行結果是另一個函式的引數,那這樣就需要有個累計的過程,綜合以上2點,想到陣列中有個reduce函式。這裡我們來看看reduce函式:
arr.reduce(callback[, initialValue])複製程式碼
其中 callback是執行陣列中每個值的函式,它包含四個引數:
accumulator 累加器累加回撥的返回值; 它是上一次呼叫回撥時返回的累積值,或initialValue(如下所示)。
currentValue 陣列中正在處理的元素。
- currentIndex[可選] 陣列中正在處理的當前元素的索引。 如果提供了initialValue,則索引號為0,否則為索引為1。
- array[可選] 呼叫reduce的陣列
initialValue
[可選] 用作第一個呼叫 callback的第一個引數的值。 如果沒有提供初始值,則將使用陣列中的第一個元素。 在沒有初始值的空陣列上呼叫 reduce 將報錯。
下面的例子求陣列成員之和。
[1, 2, 3, 4, 5].reduce(function(x, y){
console.log(x, y)
return x + y;
});
// 1 2
// 3 3
// 6 4
// 10 5
//最後結果:15複製程式碼
上面程式碼中,第一輪執行,x是陣列的第一個成員,y是陣列的第二個成員。從第二輪開始,x為上一輪的返回值,y為當前陣列成員,直到遍歷完所有成員,返回最後一輪計算後的x。
利用reduce方法,可以寫一個陣列求和的sum方法。
Array.prototype.sum = function (){
return this.reduce(function (pre, next) {
return pre + next;
})
};
[3, 4, 5, 6, 10].sum()
// 28複製程式碼
如果要對累積變數指定初值,可以把它放在reduce方法的第二個引數。
[1, 2, 3, 4, 5].reduce(function(x, y){
return x + y;
}, 10);
// 25複製程式碼
上面程式碼指定引數x的初值為10,所以陣列從10開始累加,最終結果為25。注意,這時y是從陣列的第一個成員開始遍歷。
第二個引數相當於設定了預設值,處理空陣列時尤其有用。
function add(prev, cur) {
return prev + cur;
}
[].reduce(add)
// TypeError: Reduce of empty array with no initial value
[].reduce(add, 1)
// 1複製程式碼
上面程式碼中,由於空陣列取不到初始值,reduce方法會報錯。這時,加上第二個引數,就能保證總是會返回一個值。
解決
在上面我們瞭解學習reduce之後,我們可以開始來解決這道題了,看程式碼:
function chained(funcs) {
return function(input){
return funcs.reduce(function(input, fn){ return fn(input) }, input);
}
}複製程式碼
驗證一下
function f1(x){ return x*2 }
function f2(x){ return x+2 }
function f3(x){ return Math.pow(x,2) }
function f4(x){ return x.split("").concat().reverse().join("").split(" ")}
function f5(xs){ return xs.concat().reverse() }
function f6(xs){ return xs.join("_") }
Test.assertEquals( chained([f1,f2,f3])(0), 4 )
Test.assertEquals( chained([f1,f2,f3])(2), 36 )
Test.assertEquals( chained([f3,f2,f1])(2), 12 )
Test.assertEquals(chained([f4,f5,f6])("lorem ipsum"), "merol_muspi")複製程式碼
嗯,很好,驗證通過的,沒什麼問題!
進一步思考
問題是解決了,但是認真想想一下,這裡的題目的需求是不是和redux中compose函式實現的需求一樣呢,嗯,我們來看看compose是怎麼實現的。
export default function compose(...funcs) {
if (funcs.length === 0) {
return arg => arg
}
if (funcs.length === 1) {
return funcs[0]
}
return funcs.reduce((a, b) => (...args) => a(b(...args)))
}複製程式碼
用es6寫的,關於es6的知識,可以看 ECMAScript 6 入門或者es6的十大特性
參考這個compose函式的寫法,我們來解一下上面那道題,
function chained(...funcs) {
if (funcs.length === 0) {
return arg => arg
}
if (funcs.length === 1) {
return funcs[0]
}
return funcs.reduce((a, b) => (...args) => a(b(...args)))
}複製程式碼
驗證一下:
function f1(x){ return x*2 }
function f2(x){ return x+2 }
function f3(x){ return Math.pow(x,2) }
function f4(x){ return x.split("").concat().reverse().join("").split(" ")}
function f5(xs){ return xs.concat().reverse() }
function f6(xs){ return xs.join("_") }
Test.assertEquals( chained(f1,f2,f3)(0), 4 )
Test.assertEquals( chained(f1,f2,f3)(2), 36 )
Test.assertEquals( chained(f3,f2,f1)(2), 12 )
Test.assertEquals(chained(f4,f5,f6)("lorem ipsum"), "merol_muspi")複製程式碼
waht? 怎麼會只通過一個,原來是順序反了,題目要求是從右到左累計的,所以這裡我們就需要用到reduce函式的兄弟函式reduceRight了,關於兩者的區別,
reduce
是從左到右處理(從第一個成員到最後一個成員),reduceRight
則是從右到左(從最後一個成員到第一個成員),其他完全一樣。
最終程式碼:
function chained(...funcs) {
if (funcs.length === 0) {
return arg => arg
}
if (funcs.length === 1) {
return funcs[0]
}
return funcs.reduceRight((a, b) => (...args) => a(b(...args)))
}複製程式碼
或者
function chained(...funcs) {
if (funcs.length === 0) {
return arg => arg
}
if (funcs.length === 1) {
return funcs[0]
}
return funcs.reduce((a, b) => (...args) => b(a(...args)))
}複製程式碼
嗯,完美。