在專案實踐中用更優雅的方式處理陣列問題

Jrain發表於2019-01-16

寫於 2017.07.04

在專案實踐中用更優雅的方式處理陣列問題

在最近的專案中,遇到了比較多處理陣列的場景,比如要對陣列裡面某個元素的某一個欄位進行抽取歸類,或者判斷陣列當中的某個元素是否符滿足判斷條件等。

網上關於使用ES5新的的API來代替for迴圈的文章已經非常多,它們有的詳細討論了API的用法,有的詳細分析各自的效能,還有的整理了使用中的注意事項。因此,本文不再對這些API的詳細使用方式進行贅述,僅僅從個人角度出發,整理歸納一些在專案實踐中遇到的能夠更加優雅處理陣列遍歷的例子。

1、使用Set處理陣列去重和元素剔除問題

Set是es6新增的一種資料結構,它和陣列非常相似,但是成員的值都是唯一的,沒有重複的值。它提供了4個語義化的API:

  1. add(value):新增某個值,返回Set結構本身。
  2. delete(value):刪除某個值,返回一個布林值,表示刪除是否成功。
  3. has(value):返回一個布林值,表示該值是否為Set的成員。
  4. clear():清除所有成員,沒有返回值。

參考自@阮一峰 老師的《ECMAScript 6 入門》

那麼我們可以用Set來幹嘛呢?

第一個用法,陣列去重。對於一個一維陣列,我們可以先把它轉化成Set,再配合...解構運算子重新轉化為陣列,達到去重的目的。請看例子:

const arr = [1, 1, 2, 2, 3, 4, 5, 5]

const newArr = [...new Set(arr)]

console.log(newArr)

// [1, 2, 3, 4, 5]
複製程式碼

值得注意的是,這個方法不能對元素為“物件”的陣列奏效:

const arr = [{ name: 'Alice', age: 12 }, { name: 'Alice', age: 12 }, { name: 'Bob', age: 13 }]

const newArr = [...new Set(arr)]

console.log(newArr)

// [{ name: 'Alice', age: 12 }, { name: 'Alice', age: 12 }, { name: 'Bob', age: 13 }]
複製程式碼

這是因為Set判斷元素是否重複的辦法類似於===運算子,兩個物件總是不相等的。

除了去重,Set提供的delete()方法也是非常實用。在以往的做法中,如果要刪除陣列中指定的元素,我們需要先獲取該元素所在下標,然後通過splice()方法去刪除對應下標的元素,在理解上容易引起混亂:

// 我想刪除陣列當中值為2的元素
const arr = [1, 2, 3]
const index = arr.indexOf(2)
if (index !== -1) {
    arr.splice(index, 1)
}

console.log(arr)

// [1, 3]
複製程式碼

使用Set就清晰多了:

const arr = [1, 2, 3]
const set = new Set(arr)
set.delete(2)
arr = [...set]

console.log(arr)

// [1, 3]
複製程式碼

2、 使用map()方法和物件解構語法提取欄位

請求後臺介面返回的資料中,很可能會遇到下面這種資料格式:

studentInfo = [
  { name: 'Alice', age: 18, no: 2 },
  { name: 'Bob', age: 16, no: 5 },
  { name: 'Candy', age: 17, no: 3 },
  { name: 'Den', age: 18, no: 4 },
  { name: 'Eve', age: 16, no: 1 },
]
複製程式碼

當我們要獲取姓名列表、年齡列表和編號列表的時候,我們可以通過map()再配合物件的解構語法方便快捷地進行處理:

const nameList = studentInfo.map(({ name }) => name)
const ageList = studentInfo.map(({ age }) => age)
const noList = studentInfo.map(({ no }) => no)

// nameList: [ 'Alice', 'Bob', 'Candy', 'Den', 'Eve' ]
// ageList: [ 18, 16, 17, 18, 16 ]
// noList: [ 2, 5, 3, 4, 1 ]
複製程式碼

3、使用filter()方法和物件解構語法過濾陣列

接上上面的例子,如果我想獲取一個“年齡小於等於17歲”的新列表,應該怎麼做呢?類似map()方法,我們可以用filter()方法進行過濾:

const newStudentInfo = studentInfo.filter(({ age }) => {
  return age <= 17
})

/*
newStudentInfo: [
  { name: 'Bob', age: 16, no: 5 },
  { name: 'Candy', age: 17, no: 3 },
  { name: 'Eve', age: 16, no: 1 }
]
*/
複製程式碼

4、藉助includes()方法求兩個陣列的差集

假設我們有以下兩個陣列:

var a = [1, 2, {s:3}, {s:4}, {s:5}]
var b = [{s:2}, {s:3}, {s:4}, 'a']
複製程式碼

我們應該如何找到它們的差集呢?傳統的方法可能需要把它們以Object形式hash化,但其實我們可以通過.includes()方法更加優雅方便地找出差集,程式碼如下:

var a = [1, 2, {s:3}, {s:4}, {s:5}].map(item => JSON.stringify(item))
var b = [{s:2}, {s:3}, {s:4}, 'a'].map(item => JSON.stringify(item))

var diff = a.concat(b)
            .filter(v => !a.includes(v) || !b.includes(v))
            .map(item => JSON.parse(item))
            
// diff: [1, 2, {s:5}, {s:2}, "a"]
複製程式碼

至於為什麼要JSON.stringify(),是因為要對比兩個“物件元素”是否相等,是無法直接以“物件”形式比較的(永遠返回不相等)。

5、後記

本文篇幅較短,難度也相對簡單,以上都是一些平時實踐中發現的技巧,希望可以對讀者們有所啟發。如果你也有一些比較優雅好玩的技巧,不妨和我交流分享喔~

相關文章