Array 原型的 forEach 有多好用? 學會高階函數之後都不想寫 JavaScript 以外的程式語言了

realdennis
8 min readJan 7, 2019

--

本篇文章主要介紹上次沒說完的 Array 高階函數,如 forEachmapfilter ,另外還會再說說其他幾個偏冷門,但是也很好用的 someevery

看完這篇文章的同學,最大的好處就是不用在每次遍尋陣列之前,提取陣列的 length ,甚至在 map 、 reduce 與 filter 後,可以繼續鏈式的方式調用其他高階函數(為什麼可以?)。

前言 — 什麼是高階函數?

在文章開始前先來聊聊什麼是高階函數,高階函數是至少滿足下列一個條件的函數:(謝謝你 維基百科)

  1. 接受一個或多個函數作為輸入
  2. 輸出一個函數

1 的話很明顯就是本篇文章想討論的,2 的話則是之前提到的科里化技巧。

目錄

  • forEach 解決了什麼問題?
  • 聊聊 map 的方便性 再來說說 reduce 與 filter
  • 高階函數的組合技
  • 好用的 each 與 some

forEach 解決了什麼問題?

在陣列中,最常見的操作是撰寫迴圈,透過迴圈去把每個索引(index)的元素(item)取出來做運算,在一般迴圈的寫法如下。

for( let i = 0 ; i < arr.length ; i++ ){
console.log(arr[i]);
}

在這種做法中,需要不斷透過 index 去取得陣列的對應 item,在將它傳遞進去打印的函數裡頭,我們也必須設定 index 的邊界條件。

那麼同樣的行為在 forEach 中呢?

arr.forEach(item => console.log(item))

那麼來說文解字事件, forEach 的語義是 for each …(對於每個…),在陣列上即是代表,對於每個去做操作。自然他會將每一個元素去傳遞進去console.log ,而不需要檢查邊界條件。

可是我的迴圈可能比較特別耶? 比如打印當前 index

這時候就要來介紹這個高階函數的格式了, forEach 允許把三個參數傳遞進去 callback 裡頭,分別是 item(element)indexarray(先不討論)

const callback = (item,index) => {
if(index%2===0) return;
console.log(item);
}
[0,1,2,3,4,5,6,7].forEach(callback)

因為可以得到 index 的值,所以迴圈執行條件可以撰寫在 callback 裡頭,有了 itemindex ,基本上能做到所有一般迴圈能做到的所有事。

習慣了這種高階函數的操作方式?讓我們來講講 map

陣列元素並非只是一個值(value)這麼簡單,陣列的元素可能是一個物件,而我們常常在迴圈裡頭幹的另一件事,就是把我們感興趣的資料,另外拉出來成為一個新的陣列。

比如範例陣列如下character = [{name:'JOJO',height:195,weight:80},{name:'another',height:180,weight:77},...] 如果我們想抽取身高組成一個新的身高陣列。

let hArr = [];
character.forEach(item=>hArr.push(item.height));

雖然透過 forEach 已經簡化了不少的操作,但是把一個陣列映射到另外一個陣列在數學上、函數運算上其實是相當常見的,於是我們有了 map 方法。

map 方法是什麼?他允許我們把原有的陣列映射到另外一個新的陣列,在回調函數裡面你只要給定條件,它就能對應的東西映射進去。

let hArr = character.map( item => item.height );

輕鬆寫意。

注意:箭頭函數如果沒有用{} 抱起來,意味著直接 return ,上述的操作可以視為下面操作的一個 shortcut 。

let hArr = character.map( item => {
return item.height;
});

如果你可以接受這種模式,那繼續來看看 filter 吧!

在 for loop 除了把 item 給印出來以外,另一件我們最常幹的事,就是把陣列中我們感興趣的部分抽出來。

用上面的例子來說,如果我們希望把 偶數與奇數 index 的值分別放在另外兩個陣列裡頭,假設 arr = [0,1,2,3,4,5,6,7]

forEach :

let oddArr = [], evenArr=[];arr.forEach((item,index)=>{ if(index%2===0) oddArr.push(item);
else evenArr.push(item)
})

這時候來講講 filter 方法,filter 方法允許我們在 callback 裡頭做一些條件篩選,最後依照 return 的結果來判斷要不要把陣列到另一個陣列裡頭。

let oddArr = arr.map( (item,index) => index%2===0 )
// 回傳一個原始陣列的偶數index之新陣列

filter可以做到什麼很方 bang 的事呢?

當我們得到一個關於AV女優的資訊的陣列,可能如下avArray = [{year:55},{year:18},{year:19},{year:35},{year:23}] ,幼齒愛好者表示:「拜託給我 20 歲以下的就好!」

let under20 = avArray.filter(girl=> girl.year <= 20 )// [{year:18},{year:19}]

偏冷門卻異常好用的高階函數 — some & each

針對語義來做說明, some 的意思表示,是否有一些滿足,而 each 則是每一個都要滿足。

舉例來說:

每一個人都超過 30 歲嗎? each

是不是有人超過 30 歲? some

透過這種例子我們來看看 some()& each()

在迴圈的操作裡面我們常常需要去做一個檢查的動作,比如檢查是否全部符合我們想要的,不符合的話剔除(如同剛剛的 filter ),或我們根本只是想得到一個是非結果。

就拿一個簡單的例子來舉例吧,我們想知道 nums = [0,1233,50,33,21] 是不是存在大於 1000 的數字。

在迴圈遍尋時,可能需要撰寫條件,且在條件觸發時把迴圈給 break 掉。

let someOver1000 = false;
for(let i=0;i<nums.length;i++){
if(nums[i]>1000){
someOver1000 = true;
break;
}
}

這時候好用的 some 坐在一旁笑而不語。

let someOver1000 = nums.some(value=>value>1000)
// someOver1000 = true

同理 each 的做法也是一模模一樣樣,只是它的條件比較嚴苛一點,需要全部都符合條件才會回傳 true 。

比如是否全部小於 10

傳統迴圈的做法:

let count = 0;for(let i=0 ; i<nums.length ; i++ ){

if(nums[i]>10) count++;
}
let eachUnder10 = (count === nums.length);

each 解法:

let eachUnder10 = nums.each(value => value < 10);

輕鬆寫意。

來聊聊高階函數的組合技

我們從上面的介紹可以知道某幾個高階函數會回傳一個新的 Array ,而我們介紹的高階函數中正是存在於 Array 的原型中,所以我們理所當然的可以繼續在後頭使用高階函數。

直接來一個非常非常容易遇到的題目 — 組合 query string。

在 GET 請求裡面,我們常常要在 URL 中組合出一個 queryString ,它大概是長成這樣子 ?param1=10&param2=20&param3=30

已知我們目前有一物件長得像這樣:

var payload = {
param1:10,
param2:20,
param3:30,
notImportant:40,
blablabla:50
}

我們要想盡辦法把他變成 queryString 的形式

Object.keys() 拿到 key array

Object.keys(payload) 會回傳陣列 ["param1", "param2", "param3", "notImportant", "blablabla"]

步驟一:先把 key 中不是 param 的部分過濾掉 (filter)

步驟二:把對應的 key=value 映射出來

步驟三:把陣列透過 join方法把 & 插進去

好!那我們就開始吧!

Object.keys(payload)
.filter(key => key.includes('param'))
.map(key=> `${key}=${payload[key]}` )
.join('&')

param1=10&param2=20&param3=30 大功告成!

最後記得前面需要一個? ,且需要encodeURIComponent 包裝一下,不過這邊我們就先不討論了。主要讓大家感受一下高階函數的好處!

--

--