陳建良 已發佈 2019-11-18

不再迷失在 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
images

images

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 的方式將內容推進去,此時兩種做法會得到一樣的結果。

images

images

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

images

map 卻只是把 item.support > 40 的值 Boolean ( true / false ) 回傳,跟我們預期想要的不同。

images

那假如我們想使用 map 也做到一樣的結果呢? 來看一下下列程式碼

let mapBrowser = browser.map(function (item, index) {
  if(item.support > 40) {
      return item;
  }
});

此時我們使用 console.table(mapBrowser); 觀察一下結果,會發現不符合條件的元素他一樣會回傳一個值 ( undefined ),不符合我們想要的資料結構,因此 map 並不適合使用在 "過濾" 這種情境下。

images

到這邊可能有人會有一個疑問,那 forEach 有辦法做到嗎? 讓我們來看看。

// 前面有提到 forEach 要產生新陣列必須先定義一個空陣列
let forEachBrowser = [];
browser.forEach(function(item, index) {
  if(item.support > 40) {
      // 將符合條件的 item 推進去
    forEachBrowser.push(item);
  }
});

此時得到的結果跟使用 filter 是一樣的,但是會發現程式碼並沒有 filter 直覺、乾淨,因此若是要過濾資料還是建議使用 filter 來操作。

images

images

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;
  }
});

images

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 篩選出來的結果一樣。

images

images

總結

這次是我的一次撰寫技術文章,之前都是在 HackMD 自己記錄一些上課的筆記,希望這篇文章能讓一些同學對於陣列操作能有一些基本的了解,那我們下次見囉!

參考資料

鐵人賽:常用陣列方法 | 卡斯伯 Blog - 前端,沒有極限

[ Alex 宅幹嘛 ] 👨‍💻 深入淺出 Javascript30 快速導覽:Day 4:Array Cardio Day 1

關於筆者

暱稱:陳建良

文章列表 文章列表