思路
與之前兩篇文章( map()的實現 ,filter()的實現 )中的迭代方法不一樣,reduce() 是歸併方法。
reduce 接收兩個參數:
- 第一個參數是在每一項上調用的函數
- 該函數接收 4 個參數:
- 前一個值 prev
當前值 cur
項的索引 index
數組對象 array
- 前一個值 prev
- 該函數接收 4 個參數:
- 第二個可選參數是作爲歸併基礎的初始值
reduce 方法返回一個最終的值。
代碼表示:
arr.reduce(function(prev, cur, index, arr){}, initialValue)
歸併
與之前的迭代不同,歸並不是對每一項都執行目標函數,而是可以概括爲如下兩步:
- 不斷地對數組的前兩項“取出”,對其執行目標函數,計算得到的返回值
- 把上述返回值“填回”數組首部,作爲新的 array[0]
- 持續循環執行這個過程,直到數組中每一項都訪問了一次
- 返回最終結果
舉例說明
對數組 [1,2,3] 歸併執行 (prev, cur) => prev + cur
,流程如圖:
[1, 2, 3] // 取出 1 + 2 ,填回 3
[3, 3] // 取出 3 + 3 ,填回 6
[6] // 最終結果爲 6
所以得到 6 。
實現
第一版
根據這個思路,得到第一版代碼如下
// 第一版
Array.prototype.fakeReduce = function fakeReduce(fn, base) {
// let arr = base ? this.unshift(base) : this;// 首進,返回新數組的長度,影響原數組 故不能這麼寫
let initialArr = this;
let arr = initialArr.concat(); //得到副本
if (base) arr.unshift(base); // 當存在歸併基礎值的參數時,將其從數組首部推入
let index;
while (arr.length > 2) {
index = initialArr.length - arr.length + 1;
let newValue = fn.call(null, arr[0], arr[1], index, initialArr);
arr.splice(0, 2); // 刪除前兩項,影響原數組
arr.unshift(newValue);// 把 fn(arr[0],arr[1]) 的結果從數組首部推入
}
index += 1;
let result = fn.call(null, arr[0], arr[1], index, initialArr);
return result;
};
注意點:
隊列方法
unshift()
- 可以從數組首部加入任意個項,
- 返回值是新數組的長度
- 影響原數組
splice()
方法,高程三將其譽爲最強大的數組方法- 刪除任意數量的項
- 指定 2 個參數: (刪除起始位置, 刪除項個數)
- 插入任意數量的項
- 指定 3 個參數: (起始位置,0,要插入的項)
- 第二個參數 0 即爲要刪除的個數
- 替換,即刪除任意數量的項的同時,插入任意數量的項
- 指定 3 個參數:(起始位置,要刪除的個數, 要插入的任意數量的項)
- 返回值
- 始終是一個數組,包含從原始數組中刪除的項。
- 若未刪除任何項,返回空數組
- 影響原數組
- 刪除任意數量的項
改進版
從上面的總結可以看出,splice()
方法完全可以取代 unshift()
方法。
而且,第一版中存在一些重複代碼,也可以改進。
由此得到第二版代碼
// 第二版
Array.prototype.fakeReduce = function fakeReduce(fn, base) {
let initialArr = this;
let arr = initialArr.concat();
if (base) arr.unshift(base);
let index, newValue;
while (arr.length > 1) {
index = initialArr.length - arr.length + 1;
newValue = fn.call(null, arr[0], arr[1], index, initialArr);
arr.splice(0, 2, newValue); // 直接用 splice 實現替換
}
return newValue;
};
檢測:
let arr = [1, 2, 3, 4, 5];
let sum = arr.fakeReduce((prev, cur, index, arr) => {
console.log(prev, cur, index, arr);
return prev * cur;
}, 100);
console.log(sum);
輸出:
100 1 0 [ 1, 2, 3, 4, 5 ]
100 2 1 [ 1, 2, 3, 4, 5 ]
200 3 2 [ 1, 2, 3, 4, 5 ]
600 4 3 [ 1, 2, 3, 4, 5 ]
2400 5 4 [ 1, 2, 3, 4, 5 ]
12000
最後加上類型檢測等
// 第三版
Array.prototype.fakeReduce = function fakeReduce(fn, base) {
if (typeof fn !== "function") {
throw new TypeError("arguments[0] is not a function");
}
let initialArr = this;
let arr = initialArr.concat();
if (base) arr.unshift(base);
let index, newValue;
while (arr.length > 1) {
index = initialArr.length - arr.length + 1;
newValue = fn.call(null, arr[0], arr[1], index, initialArr);
arr.splice(0, 2, newValue); // 直接用 splice 實現替換
}
return newValue;
};