答え
Set に入れて配列に戻します。
順序は保たれます。残るのは、それぞれの値が最初に現れた位置です。
なぜこれで消えるのか
Set は同じ値を2つ持てない入れ物です。入れようとしても、すでにあれば無視されます。
[...set] は、その Set を入れた順で配列に戻す書き方です。
別のやり方
| 書き方 | 計算量 | オブジェクトの重複 | NaN | 向いている場面 |
|---|---|---|---|---|
[...new Set(arr)] |
O(n) | 消えない | 1つにまとまる | ほぼ常にこれ |
filter + indexOf |
O(n²) | 消えない | 全部残る | 古い環境しか無いとき |
Map にキーで入れる |
O(n) | 消せる | — | オブジェクトの配列 |
filter + indexOf
Set が使えない古い環境向けの書き方です。
const users = [
{ id: 1, name: 'あかり' },
{ id: 1, name: 'あかり' },
];
console.log([...new Set(users)].length);
console.log(users[0] === users[1]);「自分より前に同じ値が無いものだけ残す」という意味です。
indexOf が毎回先頭から探すので、要素が増えると急に遅くなります。
つまずきどころ
NaN は indexOf では消えない
indexOf は === で比べます。ところが NaN === NaN は false なので、
indexOf は NaN を見つけられません。
filter + indexOf は NaN を全部落としました。indexOf が -1 を返し、
-1 === i がどの位置でも成り立たないためです。
Set は SameValueZero という比べ方を使うので、NaN 同士は同じとみなします。
だから NaN は1つだけ残ります。
比べ方が何種類あるのかは
なぜ NaN === NaN は false なのか にまとめてあります。
数値が混ざる配列では Set を使ってください。
オブジェクトの配列では消えない
Set も indexOf も、オブジェクトは参照で比べます。
中身が同じでも、別々に作ったオブジェクトは別物です。
見た目が同じでも消えません。何をもって「同じ」とするかを自分で決める必要があります。 参照とコピーの話は オブジェクトを結合する にもまとめてあります。
オブジェクトを id で1つにまとめる
Map はキーが重複すると上書きします。これを使います。
users.map(u => [u.id, u]) で [キー, 値] の組を作り、Map に入れています。
同じ id は上書きされるので、後から来たものが残ります。
先に来たほうを残したいなら、すでにあるキーは入れないようにします。
reverse() を2回かけて入れ替える書き方もありますが、順序が直感と合わず間違えやすいので勧めません。has() で見てから入れるほうが、読んで分かります。
元の配列は変わらない
ここに出てきた書き方は、どれも新しい配列を作ります。元の配列はそのままです。
絞り込みの考え方そのものは filter() の記事にまとめてあります。