1. 程式人生 > >為什麼應該用map和filter替換forEach?

為什麼應該用map和filter替換forEach?

當你需要拷貝一個數組的全部或者部分到一個新陣列的時候,優先使用map和filter而不是forEach。

諮詢工作的好處之一是我可以看到無數的專案。這些專案在規模、使用的程式語言和開發人員的能力方面差別很大。雖然有很多我覺得應該廢棄的模式,但是在JavaScript中,我覺得最應該廢棄的是使用forEach建立新的陣列。事實上,這個模式非常簡單,看起來如下所示:

const kids = [];
people.forEach(person => {
  if (person.age < 15) {
    kids.push({ id: person.id, name:
person.name }); } });

上面程式碼做的操作就是處理包含所有人的陣列,並找出年齡小於15的人。然後把每一個符合條件的’孩子‘的部分屬性組成的新物件新增到kids陣列中。

雖然可以滿足需求,但是有一種勢在必行的編碼方式(檢視程式設計正規化)。所以,你可能會想哪裡出了問題?要理解這一點,讓我們先熟悉兩個”朋友“:map和filter。

map & filter

map和filter是在2015年作為ES6特徵集的一部分引入到JavaScript中的。它們是陣列的方法,允許在JavaScript中使用更函式式的編碼風格。和在函數語言程式設計的世界裡一樣,這兩個方法也不會修改原陣列,而是返回一個新陣列。它們都接受一個型別是函式的單一變數。然後,這個函式會在原陣列的每一項上被呼叫去產生最終結果。讓我們看下這兩個方法做了什麼:

  • map:每一項呼叫函式的返回結果會放在這個方法返回的新數組裡。

  • filter:每一項呼叫函式的返回結果決定這一項是否會被該方法返回的陣列包含。

類似的還有一個方法,只是很少被用到,也就是reduce。

以下是檢視實際操作的簡單例子:

const numbers = [1, 2, 3, 4, 5];
const doubled = numbers.map(number => number * 2); // [2, 4, 6, 8, 10]
const even = numbers.filter(number => number % 2 === 0
); // [2, 4]

現在我們知道map和filter是幹什麼的了,接下來我們會看到一個例子,在這個例子中會展示我更偏向於怎麼寫前面的例子:

const kids = people
  .filter(person => person.age < 15)
  .map(person => ({ id: person.id, name: person.name }));

如果你想了解用在map方法裡面的lambda表示式的語法,檢視這個Stack Overflow回答瞭解詳情。
所以,這種實現方式的好處如下:

  • 關注點分離:過濾和改變資料的格式是兩個不相關的關注點,對兩個關注點分別使用各自的方法可以達到關注點分離的目的。

  • 易於測試:兩種目的都使用了簡單的純函式,使得各種行為的單元測試變得簡單。值得一提的是最初的實現版本並不是純粹的,因為依賴一些作用域外邊的狀態(keys陣列)。

  • 可讀性:因為這兩個方法有明確的目的,一個是過濾資料,一個是改變資料的格式,所以很容易看出對資料做了哪些處理。尤其是像reduce這樣的同類函式。

  • 非同步程式設計:forEach和async/await不能很好地結合在一起。但是map提供了一種有用的模式,可以和promises和async/await一起使用。更多關於這一點的內容會在下一篇部落格中介紹。

同樣值得注意的是,當你想產生副作用的時候,比如修改全域性狀態,不要使用map。尤其是當map方法的返回值並不會被儲存或者使用的時候。

總結

使用map和filter有很多好處,比如關注點分離、易於測試、可讀性和非同步程式設計的支援。因此,對我來說這是一個明智的選擇。但是,我經常遇到使用forEach的開發人員。雖然函數語言程式設計可能有點兒嚇人,但是這些方法並沒有什麼好害怕的,即使它們有一些函數語言程式設計的特徵。map和filter在響應式程式設計中也被大量的用到。由於RxJS,現在響應式程式設計在JavaScript中被越來越多的用到。但請注意,它們可能會永久地改變你的編碼方式。