天天看點

Array.prototype.reduce()Array.prototype.reduce

Array.prototype.reduce

Array.prototype.reduce()Array.prototype.reduce

歸并類方法 【MDN Docs】

reduce() 方法接收一個函數作為

累加器(accumulator)

 ,數組中的每個值(從左到右)開始縮減,最終為一個值。

reduce 為數組中的每一個元素依次執行回調函數,不包括數組中被删除或從未被指派的元素,

reducer 函數的傳回值配置設定給累計器,該傳回值在數組的每個疊代中被記住,并最後成為最終的單個結果值。

接受四個參數:

上一次回調函數的傳回值(或者初始值)

 ,

目前元素值

目前索引

調用 reduce 的數組

 。

回調函數第一次執行時,

accumulator

 和

currentValue

的取值有兩種情況:

  • 如果調用

    reduce()

    時提供了

    initialValue

    accumulator

    取值為

    initialValue

    currentValue

    取數組中的第一個值;
  • 如果沒有提供 

    initialValue

    ,那麼

    accumulator

    取數組中的第一個值,

    currentValue

    取數組中的第二個值。

是以,如果沒有提供 ​

initialValue

​ ,reduce 會從索引1的地方開始執行 callback 方法,跳過第一個索引。

如果提供 ​

initialValue

​ ,從索引0開始。

如果數組為空且沒有提供 ​

initialValue

​ ,會抛出TypeError 。

如果數組僅有一個元素(無論位置如何)并且沒有提供 ​

initialValue

​ , 或者有提供 ​

initialValue

​ 但是數組為空,那麼此唯一值将被傳回并且callback不會被執行。

一般來說,提供初始值通常更安全。

const maxCallback = (acc, cur) => {
  console.log(acc, cur);
  return { x: Math.max(acc.x, cur.x) };
};

const maxCallback1 = (acc, cur) => {
  console.log(acc, cur);
  return Math.max(acc.x, cur.x);
};

const maxCallback2 = (acc, cur) => {
  console.log(acc, cur);
  return Math.max(acc, cur);
};

// reduce() 沒有初始值
// 注意配置設定給累加器的傳回值的形式
[{ x: 2 }, { x: 22 }, { x: 42 }].reduce(maxCallback1); // NaN
[{ x: 2 }, { x: 22 }].reduce(maxCallback1); // 22
[{ x: 2 }].reduce(maxCallback1); // { x: 2 }
[].reduce(maxCallback1); // Uncaught TypeError: Reduce of empty array with no initial value

// map/reduce; 這是更好的方案,即使傳入空數組或更大數組也可正常執行
[{ x: 22 }, { x: 42 }].map((el) => el.x).reduce(maxCallback2, -Infinity);      

1 求和

const sumlist = [1, 3, 5, 7, 9, 11, 13];
const initVal = 0;
// 第二次循環方法中prev的值,是第一次循環方法傳回的結果。
function itercb(prev, curVal, curIndex, arr) {
  // console.log("先前值:", prev);
  // console.log("累加值:", curVal, ", 下标:", curIndex);
  return prev + curVal;
}

const total = sumlist.reduce(itercb, initVal);
console.log("sum total:", total);      

2 濾重

const repeatList = ["a", "aa", "aaa", "a", "a", "aa", "aaa", "aaaa", "aaa"];
function cbRepeat(prev, curVal, curIndex) {
  if (prev.includes(curVal)) {
    // 若包含,則不添加
    return prev;
  } else {
    // 添加
    return [...prev, curVal];
  }
}
const result = repeatList.reduce(cbRepeat, []);
console.log("result:", result); // ['a', 'aa', 'aaa', 'aaaa']

// another
const arr = [1, 2, 3, 4, 4, 1];
const newArr = arr.reduce((prev, cur) => {
  if (prev.indexOf(cur) === -1) {
    return prev.concat(cur);
  } else {
    return prev;
  }
}, []);

console.log(newArr); // [1, 2, 3, 4]      

各濾重方法的性能疑問

const myArray = ["a", "b", "a", "b", "c", "e", "e", "c", "d", "d", "d", "d"];

console.time("indexOf");
let myOrderedArray = myArray.reduce(function (accumulator, currentValue) {
  if (accumulator.indexOf(currentValue) === -1) {
    accumulator.push(currentValue);
  }
  return accumulator;
}, []);
console.timeEnd("indexOf"); // indexOf: 0.02685546875 ms
console.log(myOrderedArray);

console.time("sort");
let result = myArray.sort().reduce((init, current) => {
  if (init.length === 0 || init[init.length - 1] !== current) {
    init.push(current);
  }
  return init;
}, []);
console.timeEnd("sort"); // sort: 0.06787109375 ms
console.log(result); // [1,2,3,4,5]

console.time("set");
let orderedArray = Array.from(new Set(myArray));
console.timeEnd("set"); // set: 0.006103515625 ms
console.log(orderedArray);      

3 計數

const names = ["Alice", "Bob", "Tiff", "Bruce", "Alice"];

// {Alice: 2, Bob: 1, Tiff: 1, Bruce: 1}
const nameNum = names.reduce((prev, cur) => {
  // in 如果指定的屬性在指定的對象或其原型鍊中,傳回true
  // in 右操作數必須是一個對象值,可以是 new String(),但不能是 '',删除delete和指派undefined是不一樣的結果
  if (cur in prev) {
    prev[cur]++;
  } else {
    prev[cur] = 1;
  }
  return prev;
}, {});

console.log(nameNum); //{Alice: 2, Bob: 1, Tiff: 1, Bruce: 1}      
【in 操作符】

in運算符用來判斷對象是否擁有給定屬性。

類比includes,判斷數組是否擁有給定值。

【MDN 表達式和運算符】

for ... in & for ... of

const arr = ["a", "b", "c"];
const obj = { a: "aaa", b: "bbb", c: "ccc" };

for (let i in arr) {
  console.log(i); // 下标,索引值 0,1,2
}

for (let i of arr) {
  console.log(i); // 元素值 a,b,c
}

for (let i in obj) {
  console.log(i); // name,非value a,b,c
}

for (let i of obj) {
  console.log(i); // 報錯,obj is not iterable
}

// 總結:
// 對象隻能用 for in 周遊,數組可以用 for in 和 for of 周遊
// for in 周遊能擷取 index 或 name,for of 周遊能拿到數組元素值
// 隻能foa 不能foo
// fia fio 皆可,都是擷取索引(index或name)      

4 對象裡的屬性求和

var result = [
  {
    subject: "math",
    score: 10,
  },
  {
    subject: "chinese",
    score: 20,
  },
  {
    subject: "english",
    score: 30,
  },
];

var sum = result.reduce(function (prev, cur) {
  return prev + cur.score;
}, 0);

console.log(sum); // 60      

5 多元數組轉一維

const arr = [
  [1, 2],
  [
    [3, 4, [5, 6, 7], 8],
    [9, 10],
  ],
  11,
  [12, 13, [14, 15]],
];

const flttenArr = (arr) => {
  return arr.reduce((prev, cur) => {
    return prev.concat(Array.isArray(cur) ? flttenArr(cur) : cur);
  }, []);
};

flttenArr(arr); // [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]      

6 按順序運作promise

function runPromiseInSequence(arr, input) {
  return arr.reduce((promiseChain, currentFunction) => {
    /*
     * 1、Promise {<fulfilled>: 10}
     * 2、Promise {<fulfilled>: 50}
     * 3、Promise {<fulfilled>: 100}
     * 4、Promise {<fulfilled>: 300}
     */
    console.log("acc:", promiseChain);
    /*
     * 1、p1
     * 2、p2
     * 3、f3
     * 4、p4
     */
    console.log("cur:", currentFunction);
    return promiseChain.then(currentFunction);
  }, Promise.resolve(input));
}

// promise function 1
function p1(a) {
  return new Promise((resolve, reject) => {
    resolve(a * 5);
  });
}

// promise function 2
function p2(a) {
  return new Promise((resolve, reject) => {
    resolve(a * 2);
  });
}

// function 3  - will be wrapped in a resolved promise by .then()
function f3(a) {
  return a * 3;
}

// promise function 4
function p4(a) {
  return new Promise((resolve, reject) => {
    resolve(a * 4);
  });
}

const promiseArr = [p1, p2, f3, p4];
runPromiseInSequence(promiseArr, 10).then(console.log); // 1200      

7 管道運算

const double = (x) => x + x;
const triple = (x) => x * 3;
const quardple = (x) => x * 4;

const pipe =
  (...functions) =>
  (initVal) =>
    functions.reduce((acc, cur) => {
      return cur(acc);
    }, initVal);

const multiply4 = pipe(double, double);
const multiply6 = pipe(double, triple);
const multiply8 = pipe(double, quardple);
const multiply9 = pipe(triple, triple);
const multiply12 = pipe(triple, quardple);
const multiply16 = pipe(quardple, quardple);
const multiply24 = pipe(double, triple, quardple);

multiply4(10);
multiply6(10);
multiply8(10);
multiply9(10);
multiply12(10);
multiply16(10);
multiply24(10);      

利用reduce實作map

const arr = [1, 3, 5, 7, 9];

const newMappedArr = arr.map((item) => item * item);

console.log(newMappedArr); // [1, 9, 25, 49, 81]

// thisArg可選 執行 callback 函數時值被用作this。
// 不能使用箭頭函數,沒有this
Array.prototype.mapByReduce = function (cb, thisArg) {
  return this.reduce((acc, cur, index, arr) => {
    acc[index] = cb.call(thisArg, cur, index, arr);
    return acc;
  }, []);
};

// https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Array/map
const newMRArr = arr.mapByReduce((item) => item * 3);

console.log(newMRArr);      

Polyfill

// Production steps of ECMA-262, Edition 5, 15.4.4.21
// Reference: http://es5.github.io/#x15.4.4.21
// https://tc39.github.io/ecma262/#sec-array.prototype.reduce
if (!Array.prototype.reducee) {
  // 往 Array.prototype 對象上定義reduce屬性,可被調用
  Object.defineProperty(Array.prototype, "reducee", {
    // 其值為函數
    value: function (callback /*, initialValue*/) {
      // this 為 reduce 方法的調用者,即某個數組
      if (this === null) {
        throw new TypeError(
          "Array.prototype.reduce " + "called on null or undefined"
        );
      }
      if (typeof callback !== "function") {
        throw new TypeError(callback + " is not a function");
      }

      // 1. Let O be ? ToObject(this value).
      // Object 構造函數将給定的值包裝為一個新對象。this為null或undefined時,o={}
      var o = Object(this);

      // 2. Let len be ? ToLength(? Get(O, "length")).
      // >>> 按位無符号右移運算符
      var len = o.length >>> 0; // 保證是個數字,可以将結果undefined變為0

      // Steps 3, 4, 5, 6, 7
      var k = 0; // 即下标index
      var value; // 累加器,每次疊代的終值

      // 參數長度大于等于2
      if (arguments.length >= 2) {
        // 參數清單第一個是回調函數,第二個是 初始值 initialValue
        value = arguments[1];
      } else {
        // 正常有值數組,len>0,對象o中不存在k索引 ??? 排除無效的o屬性
        while (k < len && !(k in o)) {
          k++;
        }

        // 3. If len is 0 and initialValue is not present,
        //    throw a TypeError exception.
        // 空數組 & 無初始值
        if (k >= len) {
          throw new TypeError(
            "Reduce of empty array " + "with no initial value"
          );
        }
        value = o[k++];
      }

      // 8. Repeat, while k < len 一直周遊到最後一個數
      while (k < len) {
        // a. Let Pk be ! ToString(k).
        // b. Let kPresent be ? HasProperty(O, Pk).
        // c. If kPresent is true, then
        //    i.  Let kValue be ? Get(O, Pk).
        //    ii. Let accumulator be ? Call(
        //          callbackfn, undefined,
        //          « accumulator, kValue, k, O »).
        if (k in o) {
          // 記得我們給的callback裡,函數最後要return,不然value就拿不到值了
          value = callback(value, o[k], k, o);
        }

        // d. Increase k by 1.
        k++;
      }

      // 9. Return accumulator.
      return value;
    },
  });
}      

繼續閱讀