深入理解全能的 Reducer

翻譯: 劉小夕

原文鏈接:https://css-tricks.com/unders...

有一些小夥伴,對JavaScript的 reduce 方法還不夠理解,我們來看下面兩段代碼:

const nums = [1, 2, 3];
let value = 0;

for (let i = 0; i < nums.length; i++) {
    value += nums[i];
}
const nums = [1, 2, 3];
const value = nums.reduce((ac, next) => ac + next, 0);

這兩段代碼在功能上是等價的,都是數組中所有數字的總和,但是它們之間有一些理念差異。讓我們先研究一下 reducer,因爲它們功能強大,而且在編程中很重要。有成百上千篇關於 reducer 的文章,最後我會鏈接我喜歡的文章。

reducer 是什麼

要理解 reducer 的第一點也是最重要的一點是它永遠返回一個值,這個值可以是數字、字符串、數組或對象,但它始終只能是一個。reducer 對於很多場景都很適用,但是它們對於將一種邏輯應用到一組值中並最終得到一個單一結果的情況特別適用。

另外需要說明:reducer 本質上不會改變你的初始值;相反,它們會返回一些其他的東西。

讓我們回顧一下第一個例子,這樣你就可以看到這裏發生了什麼,一起看一下下面的gif:

觀看gif也許對我們所有幫助,不過還是要回歸代碼:

const nums = [1, 2, 3];
let value = 0;

for (let i = 0; i < nums.length; i++) {
    value += nums[i];
}

數組 nums ([1,2,3]) ,數組中的每個數字的第一個值將被添加到 value (0)。我們遍歷數組並將其每一項添加到 value

讓我們嘗試一下不同的方法來實現此功能:

const nums = [1, 2, 3];
const initialValue = 0;

const reducer = function (acc, item) { 
    return acc + item;
}

const total = nums.reduce(reducer, initialValue);

現在我們有了相同的數組,但這次我們不會改變初始值(即前段代碼中的 value)。這裏,我們有一個僅在開始時使用的初始值。接下來,我們可以創建一個函數,它接受一個累加器(acc)和一個項(item)。累加器是在上一次調用中返回的累積值(或者是 initialValue),是下一個回調的輸入值。在這個例子中,你可以把它想象成一個滾下一座山的雪球,當它以每一個吃過的值的大小增長時,它會吃掉它路徑中的每個值。

我們將使用 .reduce() 來接收這個函數並從初始值開始。可以使用箭頭函數簡寫:

const nums = [1, 2, 3];
const initialValue = 0;

const reducer = (acc, item) => { 
    return acc + item;
}

const total = nums.reduce(reducer, initialValue);

進一步縮短代碼長度,我們知道箭頭函數,在沒有 {} 時,默認 return;

const nums = [1, 2, 3];
const initialValue = 0;

const reducer = (acc, item) => acc + item;

const total = nums.reduce(reducer, initialValue);

現在我們可以在調用它的地方應用這個函數,也可以直接設置初始值,如下:

const nums = [1, 2, 3];

const total = nums.reduce((acc, item) => acc + item, 0);

累加器可能是一個令人生畏的術語,所以當我們在回調調用上應用邏輯時,你可以將它想象成數組的當前狀態。

調用棧

如果不清楚發生了什麼,讓我們記錄下每次迭代的情況。reduce 使用的回調函數將針對數組中的每個項運行。下面的演示將有助於更清楚地說明這一點。我使用了一個不同的數組([1,3,6]),因爲數字與索引相同可能會令人困惑。

const nums = [1, 3, 6];

const reducer4 = function (acc, item) { 
    console.log(`Acc: ${acc}, Item: ${item}, Return value: ${acc + item}`);
    return acc + item;
}
const total4 = nums.reduce(reducer4, 0);

當我們執行這段代碼時,我們會在控制檯看到以下輸出:

Acc: 0, Item: 1, Return value: 1
Acc: 1, Item: 3, Return value: 4
Acc: 4, Item: 6, Return value: 10

下面是一個更直觀的分解:

  1. 累加器(acc)從初始值(initialValue):0 開始的
  2. 然後第一個 item是1,所以返回值是1(0+1=1)
  3. 1在下次調用時成爲累加器
  4. 現在我們累加器是1(acc),item (數組的第二項)是3
  5. 返回值變爲4(1+3=4)
  6. 4在下次調用時成爲累加器,調用時的下一項 item 是6
  7. 結果是10(4+6=10),是我們的最終值,因爲6是數組中的最後一項

簡單示例

既然我們已經掌握了這一點,那麼讓我們來看看 reducer 可以做的一些常見和有用的事情。

我們有多少個X?

假設您有一個數字數組,並且希望返回一個報告這些數字在數組中出現的次數的對象。請注意,這同樣適用於字符串。

const nums = [3, 5, 6, 82, 1, 4, 3, 5, 82];

const result = nums.reduce((tally, amt) => {
    tally[amt] ? tally[amt]++ : tally[amt] = 1;
    return tally;
}, {});

console.log(result);
//{ '1': 1, '3': 2, '4': 1, '5': 2, '6': 1, '82': 2 }

最初,我們有一個數組和將要放入其中的對象。在 reducer 中,我們首先判斷這個item是否存在於累加器中,如果是存在,加1。如果不存在,添加這一項並設置爲1。最後,請返回每一項出現的次數。然後,我們運行reduce函數,同時傳遞 reducer 和初始值。

獲取一個數組並將其轉換爲顯示某些條件的對象

假設我們有一個數組,我們希望基於一組條件創建一個對象。reduce 在這裏非常適用!現在,我們希望從數組中任意一個數字項創建一個對象,並同時顯示該數字的奇數和偶數版本。

const nums = [3, 5, 6, 82, 1, 4, 3, 5, 82];

// we're going to make an object from an even and odd
// version of each instance of a number
const result = nums.reduce((acc, item) => {
  acc[item] = {
    odd: item % 2 ? item : item - 1,
    even: item % 2 ? item + 1 : item
  }
  return acc;
}, {});

console.log(result);

控制檯輸出結果:


{ '1': { odd: 1, even: 2 },
  '3': { odd: 3, even: 4 },
  '4': { odd: 3, even: 4 },
  '5': { odd: 5, even: 6 },
  '6': { odd: 5, even: 6 },
  '82': { odd: 81, even: 82 } 
}

當我們遍歷數組中的每一項時,我們爲偶數和奇數創建一個屬性,並且基於一個帶模數運算符的內聯條件,我們要麼存儲該數字,要麼將其遞增1。模算符非常適合這樣做,因爲它可以快速檢查偶數或奇數 —— 如果它可以被兩個整除,它是偶數,如果不是,它是奇數。

其它資源

在頂部,我提到了其他一些便利的文章,這些文章有助於更熟悉 reducer 的作用。以下是我的最愛:

  1. MDN文檔對此非常有用。說真的,這是他們最好的帖子之一,他們也更詳細地描述瞭如果你不提供一個初始值會發生什麼,我們在這篇文章中沒有提到。
  2. Coding Train
  3. A Drip of JavaScript

謝謝各位小夥伴願意花費寶貴的時間閱讀本文,如果本文給了您一點幫助或者是啓發,請不要吝嗇你的贊和Star,您的肯定是我前進的最大動力。 https://github.com/YvetteLau/...

關注公衆號,加入技術交流羣。

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