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 上找到