點燈坊

失くすものさえない今が強くなるチャンスよ

如何刪除 Array 中的 Object ?

Sam Xiao's Avatar 2019-12-10

ECMAScript 提供了 splice() 刪除 Array 中的 Element,但必須先提供要刪除的 Index;但若要刪除的是 Object,由於 Object 的比較是 Reference,所以實踐方式比較不一樣。

Version

macOS Catalina 10.15.1
VS Code 1.40.2
Quokka 1.0.262
ECMAScript 2015

Array.prototype.indexOf()

let data = [
  { title: 'FP in JavaScript', price: 100 },
  { title: 'RxJS in Action', price: 200 },
  { title: 'Speaking JavaScript', price: 300 }
];

let obj = { title: 'RxJS in Action', price: 200 }

let fn = obj => arr => {
  let result = arr.slice()
  let idx = result.indexOf(obj)

  if (idx !== -1) result.splice(idx, 1)

  return result
}

console.dir(fn(obj)(data))

因為 splice() 需要提供 index,直覺會使用 indexOf() 取得 index,但因為傳入為 object,所以比較的是 reference。

因為 data array 內的 object 與 obj 的 reference 不同,所以永遠找不到,因此 index-1,永遠無法如期刪除資料。

remove000

Array.prototype.findIndex()

let data = [
  { title: 'FP in JavaScript', price: 100 },
  { title: 'RxJS in Action', price: 200 },
  { title: 'Speaking JavaScript', price: 300 }
];

let obj = { title: 'RxJS in Action', price: 200 }

let fn = obj => arr => {
  let result = arr.slice()
  let idx = result.findIndex(x => x.title === obj.title && x.price === obj.price)

  if (idx !== -1) result.splice(idx, 1)

  return result
}

console.dir(fn(obj)(data))

indexOf() 有另外一個 findIndex() 版本,傳入的是 function。

由於我們要比較的是 object 內的 property value,而非 object reference,因此要使用的是 findIndex(),傳入自己要判斷的 property value。

remove001

Array.prototype.filter()

let data = [
  { title: 'FP in JavaScript', price: 100 },
  { title: 'RxJS in Action', price: 200 },
  { title: 'Speaking JavaScript', price: 300 }
];

let obj = { title: 'RxJS in Action', price: 200 }

let fn = obj => arr => arr.filter(x => !(
  x.title === obj.title && x.price === obj.price
))

console.dir(fn(obj)(data))

splice() 的缺點是直接去修改 array 本身,所以之前都必須靠 slice() 先 clone 一份 array, 而且還必須先找到 index,可改用 filter() 找到所有 不符合條件 的資料回傳。

remove002

Ramda

import { reject } from 'ramda'

let data = [
  { title: 'FP in JavaScript', price: 100 },
  { title: 'RxJS in Action', price: 200 },
  { title: 'Speaking JavaScript', price: 300 }
];

let obj = { title: 'RxJS in Action', price: 200 }

let fn = obj => reject(x => x.title === obj.title && x.price === obj.price)

console.dir(fn(obj)(data))

filter() 需搭配反向邏輯,可改用 Ramda 的 reject() 搭配正向邏輯。

remove003

Function Composition

import { reject, compose, equals } from 'ramda'

let data = [
  { title: 'FP in JavaScript', price: 100 },
  { title: 'RxJS in Action', price: 200 },
  { title: 'Speaking JavaScript', price: 300 }
];

let obj = { title: 'RxJS in Action', price: 200 }

let fn = compose(reject, equals)

console.dir(fn(obj)(data))

由於 Ramda 的 equals() 比較的不是 object 的 reference,事實上可直接將 equals()reject() 組合即可。

remove004

Conclusion

  • indexOf() 找不到 object 是因為新建立 object 有新的 reference,因此 indexOf() 找不到
  • splice() 是直接修改 array;而 filter() 是回傳新 array
  • 最簡單方式是將 equals()reject() 加以組合即可