每日一面——深入理解reduce方法

引言

有一段時間沒更新了,最近挺忙的(懶病犯了)。今天偶然想到之前去去哪兒面試的時候,面試管問我的redece題目,當時被血虐的場景。乾脆今天我們就來聊一下redece方法以及相關的應用

reduce方法j介紹

reduce(callback,initval)
其中callback函數接收4個參數:

  • Accumulator (acc) (累計器)
  • Current Value (cur) (當前值)
  • Current Index (idx) (當前索引)
  • Source Array (src) (源數組)

如果initval傳了,則索引從0開始,acc是initval,cur是arr[0]
如果initval沒有傳,則索引從1開始,acc是arr[0],cur是arr[1]
reducer 函數的返回值分配給累計器,該返回值在數組的每個迭代中被記住,並最後成爲最終的單個結果值。

例1:數組累加

const arr = [1,2,3,4,5];
console.log(arr.reduce((pre,cur)=>{return pre+cur}))
//1+2+3+4+5 15

例2: 計算總價

var product = [
    {
        name: '蘋果',
        count: 2,
        price: 5
    },
    {
        name: '桃子',
        count: 5,
        price: 2
    },
    {
        name: '西瓜',
        count: 1,
        price: 10
    }
];
var total = product.reduce((pre,cur)=>{
    return pre+cur.count*cur.price
},0)
// 30

自己實現一個reduce方法

知道了reduce的兩種模式,利用遞歸實現它並不複雜

// callback參數有4個。pre,cur,index,arr
Array.prototype.myReduce = function(callback,prev){
    for(let i = 0 ; i < this.length; i++){
    // 判斷有沒有第二個參數 
        if(!prev){ // 沒有的話複雜點,第一次拿的是兩個元素arr[0],arr[1],注意index的變化 
            prev = callback(this[i],this[i+1],i+1,this); //這裏的指針是i+1都是對的,但是下一次循環的時候i必須按是3所以需要+1
            i++; // 第一次循環了兩個變量,下次應該從第三個執行,所以向後移動
        }else{ //有的話最簡單,直接就是從arr[0]開始遞歸
            prev = callback(prev,this[i],i,this);
        }
    }
    return prev;
}

應用

好不容易學了reduce,只計算個水果價格,是不是有點太小才大用了?
我們看一看面試中能直接大幅度提升逼格的題目

1.統計字符串中出現的字母個數

相信大家都應該知道怎麼用for-in怎麼遍歷數組(字符串split出來的)並藉助一個空對象來計數
但是面試寫十幾行代碼還是比較low而且容易出錯的
我們看一個逼格高一點的實現

var arrString = 'abcdaabc';

arrString.split('').reduce((res, cur)=>{
    res[cur] ? res[cur] ++ : res[cur] = 1
    return res;
}, {})

2.數組扁平化

面試官讓實現一個原生的flat方法


// arr.flat(depth)  原生的語法 depth指定要提取嵌套數組的結構深度,默認是1 傳0不扁平話,傳Infinity則展開任意深度的嵌套數組

我們先不考慮depth 怎麼用遞歸實現一版展開任意深度的flat方法
Array.prototype.myFlat = function(){
    return this.reduce((pre,cur)=>{
        if(Array.isArray(cur)){ //是個數組再遞歸展開一層並連接
            pre = [...pre,...cur.myFlat()] //一定要有一個去扁平化的操作
        }else{  //不是數組就直接加入結果數組中
            pre.push(cur)
        }
        return pre //注意返回的是一個數組
    },[])
}

var arr = [1,2,[3,[3]],4].myFlat()
console.log(arr)


再加上depth進行遞歸
Array.prototype.myflat = function(depth=1) { //實際上邊界條件還是有一點問題
    return this.reduce((pre, cur) => {
        if (Array.isArray(cur)) {
            if (depth > 0) {
                depth--
                pre = [...pre,...cur.myflat(depth)]
            } else { // depth = 0結束遞歸
                pre.push(cur)
            }
        } else {
            pre.push(cur)
        }
        return pre
    }, [])
}

console.log([1, 2, [3,[5,[1]]], 4].myflat())   //[ 1, 2, 3, [ 5, [ 1 ] ], 4 ]
console.log([1, 2, [3,[5,[1]]], 4].myflat(0)) //[ 1, 2, [ 3, [ 5, [ 1 ] ] ], 4 ]
console.log([1, 2, [3,[5,[1]]], 4].myflat(2)) //[ 1, 2, 3, 5, [ 1 ], 4 ]
console.log([1, 2, [3,[5,[1]]], 4].myflat(Infinity)) //[ 1, 2, 3, 5, 1, 4 ]

3.實現compose函數

最後簡單介紹一下compose方法以及怎麼利用reduce 一行代碼實現。 這可是中間件的原理,大家仔細聽!

var compose = function(f,g) { //compose極簡寫法,僅做示例
    return function(x){
        return f(g(x))
    }

}
f和g都是函數,x是在他們之間通過'管道' 傳輸的值。
compose看起來就像是飼養函數,你就是飼養員。你可以選擇多個有特點的函數,讓它們結合,產生一個嶄新的函數。

有個這個函數我們能幹嘛呢
我們看一個例子:
我們現在想統計兩個字符串的總長度 並打印:總長度:xxx
一般的寫法就是寫一坨
函數式編程告訴我們不要這樣做,這麼寫耦合性太高了,不好維護,我們應該想搭積木一樣,拆成若果基礎方法,然後在拼接起來。 數據從這些方法組成的管道中流淌一遍出來就得到想要的結果了。
好處就是低耦合,可組合(像不像dota裏面的卡爾,qwe三個球搭配可以調出N多技能)
於是我們這麼寫

function sum(a,b){
    return a+b;
}
function len(str){
    return str.length
}
function addPrefix(content){
    return '總長度:'+content;
}
console.log(addPrefix(len(sum('x-1','y-2'))))  //看着怪怪的

這麼寫最後一句話真的很煩人
於是借用compose函數我們這麼寫

const newFn = compose(addPrefix,len,sum)
console.log(newFn('x-1','y-2'))

那個這個compose函數的真實寫法是啥呢
我給出3種,下次有時間再細說

//利用reduceRight
function compose(...fns){
    return function(...args){
        let lastFn = fns.pop();
        return fns.reduceRight((prev,current)=>{   //[addPrefix,len,sum]
            return current(prev) // 每次當前函數處理得是之前函數處理得結果!!! 但是首個不一樣,首個就是首個函數得執行結果
        },lastFn(...args)) //先得把參數傳入進來
    }
}


//利用reduce 
// 遞歸規律如下
// a:addPrefix b:len
// a:function(...args){return addPrefix(len(...args))}  b:sum
function compose(...fns){
    return fns.reduce(function(a,b){
        return function(...args){
            return a(b(...args))
        }
    })
}

//reduce簡化版 一行代碼搞定   面試裝B必背
let compose = (...fns)=>fns.reduce((a,b)=>(...args)=>a(b(...args)));

總結

今天的彙報就到這了,謝謝大家百忙中抽出時間閱讀,相信掌握上面reduce個個層次用例,面試這方面一定是滿滿的逼格。另外本人技術有限,如果哪寫的不對,歡迎在評論區留言指出。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章