Array 原型的 forEach 有多好用? 學會高階函數之後都不想寫 JavaScript 以外的程式語言了
本篇文章主要介紹上次沒說完的 Array 高階函數,如 forEach
、 map
、 filter
,另外還會再說說其他幾個偏冷門,但是也很好用的 some
與 every
。
看完這篇文章的同學,最大的好處就是不用在每次遍尋陣列之前,提取陣列的 length
,甚至在 map 、 reduce 與 filter 後,可以繼續鏈式的方式調用其他高階函數(為什麼可以?)。
前言 — 什麼是高階函數?
在文章開始前先來聊聊什麼是高階函數,高階函數是至少滿足下列一個條件的函數:(謝謝你 維基百科)
- 接受一個或多個函數作為輸入
- 輸出一個函數
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)
、 index
、 array(先不討論)
。
const callback = (item,index) => {
if(index%2===0) return;
console.log(item);
}[0,1,2,3,4,5,6,7].forEach(callback)
因為可以得到 index
的值,所以迴圈執行條件可以撰寫在 callback
裡頭,有了 item
、 index
,基本上能做到所有一般迴圈能做到的所有事。
習慣了這種高階函數的操作方式?讓我們來講講 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¶m2=20¶m3=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¶m2=20¶m3=30
大功告成!
最後記得前面需要一個?
,且需要encodeURIComponent
包裝一下,不過這邊我們就先不討論了。主要讓大家感受一下高階函數的好處!