使用 3 種方式刪除陣列中的物件

JavaScript 並沒有內建刪除陣列元素的 operator,必須使用一些技巧才能刪除,若陣列內放的是 object,則狀況將更加詭異,這也是 JavaScript 初學者一定會踩到的雷。

Version


Node.js 8.9.4
Angular CLI 1.6.7
Angular 5.2.3

splice()


典型錯誤寫法

app.component.ts

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
import { Component } from '@angular/core';
import { SubjectSubscriber } from '../model/subject-subscriber.model';
import { SubjectEnum } from '../enum/subject.enum';
import { SubscriberInterface } from '../interface/SubscriberInterface';

@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent implements SubscriberInterface {
message: string;
private subscribers: SubjectSubscriber[] = [];

update(message: string): void {
this.message = message;
}

onSubscribeClick() {
const subjectSubscriber: SubjectSubscriber = {
subject: SubjectEnum.FrontEnd,
subscriber: this
};

this.subscribers.push(subjectSubscriber);

console.log(this.subscribers.length);
}

onUnSubscribeClick() {
const subjectSubscriber: SubjectSubscriber = {
subject: SubjectEnum.FrontEnd,
subscriber: this
};

const index = this.subscribers.indexOf(subjectSubscriber);

if (index === -1) {
return;
}

this.subscribers.splice(index, 1);

console.log(this.subscribers.length);
}
}

13 行

1
private subscribers: SubjectSubscriber[] = [];

宣告一個陣列,注意其型別為 SubjectSubscriber,也就是 subscribers 為一個物件陣列。

19 行

1
2
3
4
5
6
7
8
9
10
onSubscribeClick() {
const subjectSubscriber: SubjectSubscriber = {
subject: SubjectEnum.FrontEnd,
subscriber: this
};

this.subscribers.push(subjectSubscriber);

console.log(this.subscribers.length);
}

準備好一個物件,使用 JavaScript array 原生的 push() 將物件新增至陣列。

30 行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
onUnSubscribeClick() {
const subjectSubscriber: SubjectSubscriber = {
subject: SubjectEnum.FrontEnd,
subscriber: this
};

const index = this.subscribers.indexOf(subjectSubscriber);

if (index === -1) {
return;
}

this.subscribers.splice(index, 1);

console.log(this.subscribers.length);
}

直覺會使用 onSubscribeClick() 方法,準備好一個物件,使用 JavaScript 原生的 indexOf() 去尋找該物件的 index,然後再加以 splice()

問題在於 subjectSubscriber 是一個全新的物件,有新的 reference, 因此 indexOf() 會找不到物件而傳回 -1

正確寫法

1
2
3
4
5
6
7
8
9
10
11
12
13
onUnSubscribeClick() {
const index = this.subscribers.findIndex(
item => item.subject === SubjectEnum.FrontEnd && item.subscriber === this
);

if (index === -1) {
return;
}

this.subscribers.splice(index, 1);

console.log(this.subscribers.length);
}

改用 findIndex(),需要傳入 arrow function,直接用物件內的 field 做判斷,這樣就可以抓到正確的 index

filter()


1
2
3
4
5
6
7
onUnSubscribeClick() {
this.subscribers = this.subscribers.filter(
item => !(item.subject === SubjectEnum.FrontEnd && item.subscriber === this)
);

console.log(this.subscribers.length);
}

另外一個方法是改用 JavaScript 原生的 filter() ,傳入 arrow function 找出所有 不符合條件 的陣列,重新指定給 subscribers

由於 filter() 會產生一個新的陣列,所以效率會較差一點,不過符合 FP 的 pure function 要求。

_.remove()


1
2
3
4
5
6
7
8
9
10
onUnSubscribeClick() {
const subjectSubscriber: SubjectSubscriber = {
subject: SubjectEnum.FrontEnd,
subscriber: this
};

_.remove(this.subscribers, subjectSubscriber);

console.log(this.subscribers.length);
}

若使用 Lodash 的 remove(),則有更直覺的寫法,一樣準備好物件,傳進 remove() 即可,雖然物件是 reference type,但 Lodash 的 remove() 會自己比較每個物件的 field 值是否相等。

Conclusion


  • indexOf() 找不到物件,並不是 indexOf() 不能使用在物件,而是常常因為新建立物件的 reference 已經改變,導致 indexOf() 找不到
  • Lodash 的 remove() 提供更直覺的方式刪除物件陣列中的元素,無論是 value type 或 reference type 都適用

Sample Code


完整的範例可以在我的 GitHub 上找到

2018-02-03