不再迷失在 JavaScript 的陣列處理
陣列的批次處理
陣列的批次處理是 JavaScript 處理資料非常重要的一環。
但是我們卻很常迷失在陣列眾多的方法之中 ( forEach、map、filter、sort、reduce......等等 )。
不清楚在哪一種 "情境" 該使用哪一種 "方法"。
因此,讓我們一起來看看陣列中有哪些常用的批次處理方法。
forEach
forEach 是對陣列內的每一個元素執行函式,以陣列 browser 為例。
情境 : 將陣列內的每一個元素都新增一個屬性 newSupport
,並且值是 support - 5
。
let browser = [
{
name: 'Google',
support: 98
},
{
name: 'IE',
support: 11
},
{
name: 'Safari',
support: 49
},
{
name: 'Firefox',
support: 86
}
]
// --------------------------------------------------------------------- //
// 此時的 item 是陣列內的每一筆資料,index 則是每筆資料的索引(從 0 開始),item 及 index 都是自己取名的值
browser.forEach(function (item, index) {
item.newSupport = item.support - 5;
});
此時我們使用 console.table(browser)
看一下 browser 目前的結果,就可以看到每一包資料都被新增了一個新的屬性 newSupport ,並且值是 support - 5
map
map 跟 forEach 其實非常地像,都是針對陣列內的每一個元素執行函式,最大的差別在於 map 會將 return 的結果存成一個新陣列,而 forEach 並不會 return 結果。
情境 : 將所有瀏覽器的名字取出來 "存成一個新的陣列",map 跟 forEach 分別有不同的做法。
let browser = [
{
name: 'Google',
support: 98
},
{
name: 'IE',
support: 11
},
{
name: 'Safari',
support: 49
},
{
name: 'Firefox',
support: 86
}
]
// --------------------------------------------------------------------- //
// map 做法,定義一個變數去接 return 回來的值
let mapBrowser = browser.map(function (item, index) {
// map 跟 forEach 最大的差別在於 map 會回傳值
return item.name;
});
// forEach 做法,預先定義一個空陣列,並使用 push 去新增資料
let forEachBrowser = [];
browser.forEach(function (item, index) {
forEachBrowser.push(item.name);
});
看出差別了嗎 ? map 會將 return 的值組合成一個新陣列,但是 forEach 並沒有辦法 return 值,因此必須先定義一個空陣列,再使用 push 的方式將內容推進去,此時兩種做法會得到一樣的結果。
filter
filter 可以把它想成一個過濾器,會將 "符合條件" 的元素 return 回來,因此很適合用在搜尋特定條件的資料,跟 map 一樣也會產生一個新陣列,可能有些人會把兩者功能搞混,讓我們來看一下差別。
情境 : 將 support > 40 的內容過濾出來
let browser = [
{
name: 'Google',
support: 98
},
{
name: 'IE',
support: 11
},
{
name: 'Safari',
support: 49
},
{
name: 'Firefox',
support: 86
}
]
// --------------------------------------------------------------------- //
// filter 寫法
let filterBrowser = browser.filter(function (item, index) {
return item.support > 40;
});
// map 寫法
let mapBrowser = browser.map(function (item, index) {
return item.support > 40;
});
filter 會將符合條件 ( item.support > 40 ) 的這三包資料 ( Google、Safari、Firefox ) return 回 filterBrowser
map 卻只是把 item.support > 40 的值 Boolean ( true / false ) 回傳,跟我們預期想要的不同。
那假如我們想使用 map 也做到一樣的結果呢? 來看一下下列程式碼
let mapBrowser = browser.map(function (item, index) {
if(item.support > 40) {
return item;
}
});
此時我們使用 console.table(mapBrowser);
觀察一下結果,會發現不符合條件的元素他一樣會回傳一個值 ( undefined ),不符合我們想要的資料結構,因此 map 並不適合使用在 "過濾" 這種情境下。
到這邊可能有人會有一個疑問,那 forEach 有辦法做到嗎? 讓我們來看看。
// 前面有提到 forEach 要產生新陣列必須先定義一個空陣列
let forEachBrowser = [];
browser.forEach(function(item, index) {
if(item.support > 40) {
// 將符合條件的 item 推進去
forEachBrowser.push(item);
}
});
此時得到的結果跟使用 filter 是一樣的,但是會發現程式碼並沒有 filter 直覺、乾淨,因此若是要過濾資料還是建議使用 filter 來操作。
sort
sort 會對陣列的元素進行排序,此方法會變更原本陣列的內容。
情境 : 將 Browser 的 support 由低至高排列
let browser = [
{
name: 'Google',
support: 98
},
{
name: 'IE',
support: 11
},
{
name: 'Safari',
support: 49
},
{
name: 'Firefox',
support: 86
},
{
name: "Edge",
support: 11
}
]
// --------------------------------------------------------------------- //
// 這邊傳入的參數跟上述幾個方法不太一樣,排序時傳入兩個參數 a、b(自定義)。
browser.sort(function(a, b) {
// 此時會將 a.support 的值跟 b.support 的值做比較
// 若是 a.support < b.support,則回傳 -1,並且把 a 排在 b 的前面
// 若是 a.support > b.support,則回傳 1,並且把 b 排在 a 的前面
// 若是 a.support = b.support,則回傳 0,並且兩者順序不變
if (a.support < b.support) {
return -1;
} else if (a.support > b.support) {
return 1;
} else {
return 0;
}
});
reduce
reduce 比較常用在"累加",我個人認為它算是 forEach 以及 map 的複合型,能夠傳入的參數比較多也比較複雜,讓我們來看一下以下範例。
情境 : 將所有瀏覽器的 support 相加
let browser = [
{
name: 'Google',
support: 98
},
{
name: 'IE',
support: 11
},
{
name: 'Safari',
support: 49
},
{
name: 'Firefox',
support: 86
}
]
// --------------------------------------------------------------------- //
// reduce 可以傳入四個參數(初始值, 目前的元素, 目前元素的索引, 陣列內容),
// 在函數後面可以傳入一個初始值,這邊傳入的是 0,讓我們來拆解一下
let supportTotal = browser.reduce(function(prev, current, index, array) {
// 第一個傳入 : prev 此時是帶入後方的初始值 0,
// 跟目前的 current.support ( Google 的 support ) 98 相加 => 98
// 第二個傳入 : prev 此時是 98,
// 跟目前的 current.support ( IE 的 support ) 11 相加 => 109
// 第三個傳入 : prev 此時是 109,
// 跟目前的 current.support ( Safari 的 support ) 49 相加 => 158
// 第四個傳入 : prev 此時是 158,
// 跟目前的 current.support ( Firefox 的 support ) 86 相加 => 244
// 最後 prev 得到的結果是 244,並且把它 return 回來
prev += current.support;
return prev;
}, 0);
接著讓我們使用 reduce 來進行前面做過的 "將 support > 40" 的元素取出來,傳進新陣列。
let reduceBrowser = browser.reduce(function(prev, current, index, array) {
// 第一個傳入 : prev 此時是空陣列,
// Google 的 support 符合條件,因此將整包 Google 物件 push 進去
// 第二個傳入 : prev 此時是 [{Google...}],
// IE 的 support 不符合條件,因此不會被 push 進去
// 第三個傳入 : prev 此時是 [{Google...}],
// Safari 的 support 符合條件,因此將整包 Safari 物件 push 進去
// 第四個傳入 : prev 此時是 [{Google..., Safari...}],
// Firefox 的 support 符合條件,因此將整包 Firefox 物件 push 進去
// 最後 return prev 的結果為 [{Google...},{Safari...},{Firefox...}]
if (current.support > 40) {
prev.push(current);
}
return prev;
}, []); // 這邊傳入預設值空陣列
最後得到的新陣列就跟前面使用 filter 以及 forEach 篩選出來的結果一樣。
總結
這次是我的一次撰寫技術文章,之前都是在 HackMD 自己記錄一些上課的筆記,希望這篇文章能讓一些同學對於陣列操作能有一些基本的了解,那我們下次見囉!
參考資料
鐵人賽:常用陣列方法 | 卡斯伯 Blog - 前端,沒有極限
[ Alex 宅幹嘛 ] 👨💻 深入淺出 Javascript30 快速導覽:Day 4:Array Cardio Day 1