天天看點

為什麼應該用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中被越來越多的用到。但請注意,它們可能會永久地改變你的編碼方式。

繼續閱讀