【JavaScript】配列の操作ってどうやるの？

JavaScriptの配列はミュータブルなので、作成したあとも操作することができます。
操作する方法はいろいろありますが、主に配列のメソッドが使えます。

この記事では、そんな配列の操作方法を紹介していきます。

:::note
この記事では、配列の要素を**追加/削除/上書き**することを、配列の操作と呼んでいます。
:::

# 先頭/末尾の要素を追加/削除する
まずは、先頭 / 末尾に要素を追加したり、先頭 / 最後の末尾を削除したりするメソッドを紹介します。

詳細な解説に入る前に、一度これらの4つのメソッドを表にまとめておきます。

|メソッド名| 場所 | 操作 |
|:--|:--|:--|
| [`push`](#push) | 末尾 | 追加
| [`pop`](#pop) | 末尾 | 削除
| [`unshift`](#unshift) | 先頭 | 追加
| [`shift`](#shift) | 先頭 | 削除

## push
配列の**末尾**に要素を**追加**するメソッドです。

```js
const numbers = [1, 2, 3]

// 4を最後に追加
numbers.push(4) // 4

numbers // [1, 2, 3, 4]
```

- 引数: 追加したい要素（たち）
- 戻り値: 追加したあとの配列の長さ
    - あまり使わない印象

詳細は[MDN](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Array/push)をご覧ください。

## pop
配列の**末尾**にある要素を**削除**するメソッドです。

```js
const numbers = [1, 2, 3]

// 最後の要素を削除する
numbers.pop() // 3

numbers // [1, 2]
```

- 引数: なし
- 戻り値: 削除した要素
    - 配列が空なら`undefined`

詳細は[MDN](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Array/pop)をご覧ください。

## unshift
配列の**先頭**に要素を**追加**するメソッドです。

```js
const numbers = [1, 2, 3]

// 先頭に0を追加する
numbers.unshift(0) // 4

numbers // [0, 1, 2, 3]
```

- 引数: 先頭に追加したい要素
- 戻り値: 追加したあとの配列の長さ
    - あまり使わない印象

詳細は[MDN](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Array/unshift)をご覧ください。

## shift
配列の**先頭**にある要素を**削除**するメソッドです。

```js
const numbers = [1, 2, 3]

// 最初の要素を削除する
numbers.pop() // 1

numbers // [2, 3]
```

- 引数: なし
- 戻り値: 削除した要素
    - 配列が空なら`undefined`

詳細は[MDN](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Array/shift)をご覧ください。


# インデックスの位置の要素を操作する
続いては、インデックスを用いて配列の要素を変更する方法です。
ここにある方法を使うと、**指定の位置に要素を追加**したり、反対に**削除**することもできます。

紹介するメソッドは下記のとおりです。

- 追加 / 削除 / 上書き: `splice`
- 上書き: `[]`（ブラケット記法）

## 追加
指定の位置に要素を追加するには、`splice`メソッドを用います。

このメソッドは3つの種類の引数を取ります。

- `start`: 操作を開始するインデックス
- `deleteCount`: 削除する要素の数
    - 今回は何も削除しないので`0`でOK
    - 次の「削除」で使う
- `item1`...`itemN`: 追加する要素たち
    - 一気に複数の要素を追加できる
    - 一つの引数に入れるのではなく、3つ目以降の引数がこれとして扱われる

例えばこんなことができます。
```js
const month = ['Jan', 'March', 'April']

// 1番目（JanとMarchの間）にFebを追加する
month.splice(1, 0, 'Feb')

month // ['Jan', 'Feb', 'March', 'April']
```

`start`のインデックスはこのようになっています。

- 配列の最初の要素**より前**: `0`
- 配列の最初の要素と次の要素の間: `1` <- 上のコードで使っているやつ
- 次の要素とその次の要素の間: `2`
- etc...

詳細は[MDN](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Array/splice)をご覧ください。

## 削除
追加のときと同じく`splice`メソッドを用います。

このメソッドは3つの種類の引数を取ります。

- `start`: 操作を開始するインデックス
- `deleteCount`: 削除する要素の数
    - ここでいくつ削除するかを決める
- `item1`...`itemN`: 追加する要素たち
    - 前の「追加」で使う

例えばこんなことができます。
```js
// 2月が2つある
const month = ['Jan', 'Feb', 'Feb', 'March', 'April']

// 1番目の要素を削除する
// （1番目から初めて１つの要素を削除する）
month.splice(1, 1, 0)

month // ['Jan', 'Feb', 'March', 'April']
```

## 上書き

`[]`というブラケット記法を用います。
構文は`変数名[インデックス] = 変更したい値`という感じです。

例えばこのような感じです。
```js
const month = ['Jan', 'Feb', 'March', 'April', 'une' /* Juneが正しい */]

// 4番目の要素をJuneに置き換える
month[4] = 'June'

console.log(month) // ['Jan', 'Feb', 'March', 'April', 'June']
```

### `splice`の場合
`splice`でもブラケット記法と同じようなことができます。
しかも、`splice`ならいくつかの要素をまとめて置き換えることができます。

```js
// 略称じゃない月
const data = ['January', 'February']

// 置き換えたい配列
const month = ['Jan', 'Feb', 'March', 'April']

// JanとFebをdataに置き換える
// （0番目から２つ削除し、dataの要素全てを追加する）
month.splice(0, 2, ...data)

month // ['January', 'February', 'March', 'April']
```

## コピーメソッド

今ある配列を変更するのではなく、変更したものを**新しい配列にしたい**場合は、コピーメソッドと呼ばれる別のメソッドが使えます。

コピーメソッドは既存の配列を変更しないので、どこにどのような影響が出るかを考える心配が減ることがあります。

それぞれのコピーメソッドはこちらです。

- `splice`: [`toSpliced`](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Array/toSpliced)
- ブラケット記法: [`with`](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Array/with)

### 何が変わるのか

直接変更するとどんなバグが出るかわからないケースとして、関数の引数を直接変更する場合が挙げられます。
```js
// 変更を加えた配列を返す関数
function test(array) {
  // これは何かやりたい処理
  array.splice(1, 2, 'elem1', 'elem2')
  return array
}

const array = ['test1', 'test2', 'test3']
const newArray = test(array) // 関数を呼び出す

newArray // ['test1', 'elem1', 'elem2']
array // ['test1', 'elem1', 'elem2'] <- こちらまで変わっている
```

このコードでは引数で受け取った`array`を`splice`で変更してしまっているので、元の配列にも影響が出てしまっています。
それが正しい挙動ならいいのですが、今回はそうではありません。

コピーメソッドである`toSpliced`を使えば、このような心配は起こらなくなります。

```js
// 変更を加えた配列を返す関数
function test(array) {
  // これは何かやりたい処理
  return array.toSpliced(1, 2, 'elem1', 'elem2')
}

const array = ['test1', 'test2', 'test3']
const newArray = test(array)

newArray // ['test1', 'elem1', 'elem2']
array // ['test1', 'test2', 'test3'] <- 変わってない
```

# それ以外の操作

ここでは詳しくは紹介しませんが、ほかにも以下の操作ができます。

| メソッド名 | できること | 対応するコピーメソッド |
|:--|:--|:--|
| [`reverse`](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Array/sort) | 要素を反転させる | [`toReversed`](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Array/toReversed)
| [`sort`](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Array/reverse) | 要素を規則に基づいて並び替える | [`toSorted`](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Array/toSorted)
| [`slice`](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Array/slice) | 一部の要素を切り出す | なし（`slice`自体がコピーを返すため）