JS Array.reduce 實現 Array.map 和 Array.filter

光明左使楊逍發表於2018-12-08

繼上一篇 Array.prototype.reduce 後,我立志要好好學習。琢磨了很久,再加上最近看了幾篇"JS 函數語言程式設計"的文章和書籍後,然後有感而發寫下了這篇文章。

Array.prototype.map 方法相信大家都用的很熟悉了,同時我也相信很多人已經自己實現了 map 函式。沒有實現過自己的map ? 沒關係,我們先用for 迴圈來實現下。

  Array.prototype.selfMap = function () {
      const ary = this
      const result = []
      const [ fn, thisArg ] = [].slice.call(arguments)
      if (typeof fn !== 'function') {
        throw new TypeError(fn + 'is not a function')  
      }
      for (let i = 0; i < ary.length; i++) {
        result.push(fn.call(thisArg, ary[i], i, ary))
      }
      return result
  }
  
  const a = new Array(1, 2, 3, 4)
  a.selfMap(item => item + 1) // [ 2, 3, 4, 5 ]
複製程式碼

實現了自己的map,還不是美滋滋。

但是,這和本文沒有半點關係,因為我是要用reduce實現map啊???

眾所周知,map函式需要傳一個函式的,還有一個可選的this引數,但是我發現第二個引數大家很少用到,也不怎麼注意,我也是這樣。

[1, 2, 3].map(function(item) {
    console.log(this)
    return item
}, { msg: 'mapping' })
複製程式碼

上面?這段程式碼塊不僅會返回一個新的陣列,還會在控制檯列印三次

 { msg: 'mapping' }
複製程式碼

有圖有真相?

JS Array.reduce 實現 Array.map 和 Array.filter

可能有的小夥伴在驗證我上面的例子時,會使用箭頭函式,然後發現總是列印window,就是下面這樣?

JS Array.reduce 實現 Array.map 和 Array.filter

然後心裡暗道“無恥小兒,竟敢騙我”。心裡苦啊,箭頭函式在宣告時就繫結了它外層的this(此例的thiswindow,而且還改變不了, 也就是說{ msg: 'mapping' }相當於白傳了)?

似乎廢話有點多額,那我們先用reduce來實現map吧(預設執行環境支援Array.prototype.reduce,如果不支援的話,那還寫個?)

    // 這次不把方法寫在Array的原型上
    const reduceMap = (fn, thisArg /*真想去掉thisArg這個引數*/ ) => {
        return (list) => {
            // 不怎麼願意寫下面這兩個判斷條件
            if (typeof fn !== 'function') {
                throw new TypeError(fn + 'is not a function')  
            }
            if (!Array.isArray(list)) {
                throw new TypeError('list must be a Array')
            }
            if (list.length === 0) return []
            return list.reduce((acc, value, index) => {
                return acc.concat([ fn.call(thisArg, value, index, list) ])
            }, [])
        }
    }
    
    // 來使用下怎麼樣?
    
    reduceMap(x => x + 1)([ 1, 2, 3 ]) // [ 2, 3, 4 ]
    
    const mapAry1 = reduceMap(function(item) {
        console.log(this)
        return item + 1
    }, { msg: 'mapping' })([ 1, 2, 3 ]) 
    // [ 2, 3, 4 ] 
    // logging { msg: 'mapping' } three times
複製程式碼

?實現的原理相信大家應該都懂吧。

打鐵當趁熱,繼續來實現filter吧。

  • for 迴圈實現版
Array.prototype.selfFilter = function () {
    const ary = this
    const result = []
    const [ fn , thisArg ] = [].slice.call(arguments)
    
    if (typeof fn !== 'function') {
        throw new TypeError(fn + 'is not a function')  
    }
    
    for (let i = 0; i < ary.length; i++) {
        if (fn.call(thisArg, ary[i], i, ary)) {
            result.push(ary[i])
        }
    }
    return result
}

const a = new Array(1, 2, 3)
a.selfFilter(item => item % 2 === 0) // [ 2 ]
a.selfFilter(function (item) {
    console.log(this)
    return item % 2 === 0
}, {})
// [ 2 ]
// logging {} three times
複製程式碼
  • reduce 實現版
// 同map, 不定義在Array的原型上
const reduceFilter = (fn, thisAry /* thisAry知不知你好討厭啊 */ )  => {
    return (list) => {
        if (typeof fn !== 'function') {
            throw new TypeError(fn + 'is not a function')  
        }
        if (!Array.isArray(list)) {
            throw new TypeError('list must be a Array')
        }
        if (list.length === 0) return []
        return list.reduce((acc, value, index) => {
            return fn.call(thisAry, value, index, list) ? acc.concat([ value ]) : acc
        }, [])
    }    
}

reduceFilter(x => x % 2 === 0)([ 1, 2, 3 ]) // [ 2 ]

複製程式碼

文章裡摻雜了些許函數語言程式設計裡面的東西,因為我也才開始學函數語言程式設計不久,就不在大佬們面前獻醜了。如果文章裡有哪裡寫的不對或者不夠準確,亦或者是覺得有寫的不好的地方,煩請各位指正,也讓我改正。

相關文章