Array.prototype.reduce 的理解與實現

Array.prototype.reduce 是 JavaScript 中比較實用的一個函數,可是不少人都沒有使用過它,由於 reduce 能作的事情其實 forEach 或者 map 函數也能作,並且比 reduce 好理解。可是 reduce 函數仍是值得去了解的。數組

reduce 函數能夠對一個數組進行遍歷,而後返回一個累計值,它使用起來比較靈活,下面瞭解一下它的用法。函數

reduce 接受兩個參數,第二個參數可選:ui

@param {Function} callback 迭代數組時,求累計值的回調函數
@param {Any} initVal 初始值,可選
複製代碼

其中,callback 函數能夠接受四個參數:spa

@param {Any} acc 累計值
@param {Any} val 當前遍歷的值
@param {Number} key 當前遍歷值的索引
@param {Array} arr 當前遍歷的數組
複製代碼

callback 接受這四個參數,通過處理後返回新的累計值,而這個累計值會做爲新的 acc 傳遞給下一個 callback 處理。直處處理完全部的數組項。獲得一個最終的累計值。prototype

reduce 接受的第二個參數是一個初始值,它是可選的。若是咱們傳遞了初始值,那麼它會做爲 acc 傳遞給第一個 callback,此時 callback 的第二個參數 val 是數組的第一項;若是咱們沒有傳遞初始值給 reduce,那麼數組的第一項會做爲累計值傳遞給 callback,數組的第二項會做爲當前項傳遞給 callback。code

示例:對象

對數組求和:索引

let arr = [1, 2, 3];
let res = arr.reduce((acc, v) => acc + v);
console.log(res); // 6
複製代碼

若是咱們傳遞一個初始值:ip

let arr = [1, 2, 3];
let res = arr.reduce((acc, v) => acc + v, 94);
console.log(res); // 100
複製代碼

利用 reduce 求和比 forEach 更加簡單,代碼也更加優雅,只須要清楚 callback 接受哪些參數,表明什麼含義就能夠了。underscore

咱們還能夠利用 reduce 作一些其餘的事情,好比對數組去重:

let arr = [1, 1, 1, 2, 3, 3, 4, 3, 2, 4];
let res = arr.reduce((acc, v) => {
  if (acc.indexOf(v) < 0) acc.push(v);
  return acc;
}, []);
console.log(res); // [1, 2, 3, 4]
複製代碼

統計數組中每一項出現的次數:

let arr = ['Jerry', 'Tom', 'Jerry', 'Cat', 'Mouse', 'Mouse'];
let res = arr.reduce((acc, v) => {
  if (acc[v] === void 0) acc[v] = 1;
  else acc[v]++;
  return acc;
}, {});
console.log(res); // {Jerry: 2, Tom: 1, Cat: 1, Mouse: 2}
複製代碼

將二維數組展開成一維數組:

let arr = [[1, 2, 3], 3, 4, [3, 5]];
let res = arr.reduce((acc, v) => {
  if (v instanceof Array) {
    return [...acc, ...v];
  } else {
    return [...acc, v];
  }
});
console.log(res); // [1, 2, 3, 3, 4, 3, 5]
複製代碼

由此能夠看出,reduce 函數仍是很實用的,可是 reduce 函數兼容性不是特別好,只支持到 IE 9,若是要在 IE 8 及如下使用的話就不行了,因此咱們能夠本身實現一下,還能夠對其作一下擴展,使其可以遍歷對象。

首先能夠實現一個最基礎的 each 函數,做爲咱們 reduce 的基礎:

/** * 遍歷對象或數組,對操做對象的屬性或元素作處理 * @param {Object|Array} param 要遍歷的對象或數組 * @param {Function} callback 回調函數 */
function each(param, callback) {
  // ...省略參數校驗
  if (param instanceof Array) {
    for (var i = 0; i < param.length; i++) {
      callback(param[i], i, param);
    }
  } else if (Object.prototype.toString.call(param) === '[object Object]') {
    for (var val in param) {
      callback(param[val], val, param);
    }
  } else {
    throw new TypeError('each 參數錯誤!');
  }
}
複製代碼

能夠看出 each 能夠遍歷對象或數組,回調函數接受三個參數:

@param {Any} v 當前遍歷項
@param {String|Number} k 當前遍歷的索引或鍵
@param {Object|Array} o 當前遍歷的對象或者數組
複製代碼

有了這個基礎函數,咱們能夠開始實現咱們的 reduce 函數了:

/** * 迭代數組、類數組對象或對象,返回一個累計值 * @param {Object|Array} param 要迭代的數組、類數組對象或對象 * @param {Function} callback 對每一項進行操做的回調函數,接收四個參數:acc 累加值、v 當前項、k 當前索引、o 當前迭代對象 * @param {Any} initVal 傳入的初始值 */
function reduce(param, callback, initVal) {
  var hasInitVal = initVal !== void 0;
  var acc = hasInitVal ? initVal : param[0];
  each(hasInitVal ? param : Array.prototype.slice.call(param, 1), function(v, k, o) {
    acc = callback(acc, v, k, o);
  });
  return acc;
}
複製代碼

能夠看到,咱們的 reduce 函數就是在 each 上面封裝了一層。根據是否傳遞了初始值 initVal 來決定遍歷的起始項。每次遍歷都接受 callback 返回的 acc 值,而後在 reduce 的最後返回 acc 累計值就能夠啦!

固然,這部分代碼有一個很嚴重的 bug,致使了咱們的 polyfill 毫無心義,那就是遍歷對象時的 for...in。這個語法和在 IE <= 9 環境下存在 bug,會沒法得到對象的屬性值,這就致使咱們所實現的 reduce 沒法在 IE 9 如下遍歷對象,可是遍歷數組仍是能夠的。對於 for...in 的這個 bug,能夠參考 underscore 是怎麼實現的,這裏暫時不研究了~

相關文章
相關標籤/搜索