reduce與redux中compose函式

極客教程發表於2017-11-30

在說compose函式之前,我們先來看一道題目:

image.png
image.png

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")複製程式碼

image.png
image.png

嗯,很好,驗證通過的,沒什麼問題!

進一步思考

問題是解決了,但是認真想想一下,這裡的題目的需求是不是和redux中compose函式實現的需求一樣呢,嗯,我們來看看compose是怎麼實現的。

github.com/reactjs/red…

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")複製程式碼

image.png
image.png

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)))
}複製程式碼

嗯,完美。

參考:
Array.prototype.reduce()

相關文章