【JavaScript】配列の要素でループするには？forofとforEachの違い

配列の各要素でループするには、主に`forof`文と`forEach`メソッドを使う方法があります。
では、これらの方法は何が違うのでしょうか。

結論から言うと、どちらも**できることはほぼ変わりません。**
しかし、ちょっと違う点もあります。

:::note warn
この記事では以下の話を扱いません。
- 通常の`for`文
- 実行速度やパフォーマンス
:::

# forEach

`forEach`は配列の**メソッドの一つ**で、配列の各要素でループするのに使われます。
例えば、以下のように配列の各要素を出力できます。

```js
const animals = ["dog", "cat", "rabbit"]

// 引数にアロー関数を入れて呼び出す
animals.forEach(animal => { // animalには要素（"dog"とか）が入る
  console.log(`Animal: ${animal}`)
})

// Animal: dog
// Animal: cat
// Animal: rabbit
```

## できること
### インデックスが使える
インデックスを使うループの場合、`forEach`のほうが簡単に実装できます。
これは`forEach`が取る関数の**第二引数がインデックスになる**からです。

例えば以下のようにすると、配列の中身をインデックスとともに出力することができます。
```js
const animals = ["dog", "cat", "rabbit"]

animals.forEach((animal, index) => { // indexにはインデックス（0始まり）が入る
  console.log(`Animal ${index}: ${animal}`)
})

// Animal 0: dog
// Animal 1: cat
// Animal 2: rabbit
```

:::note warn
`forof`でインデックスを使う場合は、配列の`entries`メソッドを使う必要があります（[後述](#インデックスを使うには一手間必要)）。
:::

## できないこと
### 制御文が使えない
`forof`文では`break`や`continue`などの制御文が使えますが、`forEach`はただの関数なので使えません。

```js
const animals = ["dog", "cat", "rabbit"]

animals.forEach(animal => {
  console.log(`Animal ${index}: ${animal}`)
  break // Syntax Error!
})
```

その代わり、`forEach`は関数を取るので`return`が使えます。
`forEach`での`return`は、`forof`内で使う`continue`と同じことができます。

```js
const animals = ["dog", "cat", "rabbit"]

animals.forEach((animal, index) => {
  if (index === 1) return // indexが1なら出力しない
  console.log(`Animal ${index}: ${animal}`)
})

// Animal 0: dog
// Animal 2: rabbit
```

### ループを中断できない
`forEach`のループは途中で中断できません。

`forof`文では`break`を使うと中断できますが、`forEach`では関数内で**例外を投げる**必要があります。

```js
[1, 2, 5].forEach(x => {
  if (x % 2 === 0) throw x // 偶数がひとつでも見つかったら中断
  console.log(x)
})

// 1
// Uncaught 2
```

例外を投げる場合、その例外がどこまで影響を与えるのかを考える必要があります。

上のコードのように`forEach`の呼び出しを`try-catch`で囲んでいない場合、想定外のところまでエラーが伝播するかもしれません。

:::note
これは`forof`文でも`同じで、途中でエラーを投げるとループを中断できます。

しかし`forof`にはその役割を果たす`break`があります。
そのため**ループを中断するためだけにエラーを投げることはない**です。
:::

# `forof`
`forof`文は[反復可能オブジェクト](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Iteration_protocols#%E5%8F%8D%E5%BE%A9%E5%8F%AF%E8%83%BD%E3%83%97%E3%83%AD%E3%83%88%E3%82%B3%E3%83%AB)をループする構文です。

```js
const array = [1, 4, 5]

// for (const 要素を入れる変数名 of 反復可能オブジェクト) { 処理 }
for (const num of array) {
  console.log(num)
}
```

反復可能オブジェクトには例えば配列（`Array`）がありますが、それ以外にもいくつかあります。
また、反復可能オブジェクトは**自分で作る**こともできます。（[詳細](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Iteration_protocols#%E5%8F%8D%E5%BE%A9%E5%8F%AF%E8%83%BD%E3%83%97%E3%83%AD%E3%83%88%E3%82%B3%E3%83%AB)）

## できること

### 制御文が使える
`forof`文では`break`や`continue`が使えます。

それぞれの文の動作は大体こんな感じです。

- `break`: ループ全体を中断する
- `continue`: 次のループに行く

```js
for (const num of [1, 4, 5] {
  // 偶数だったら次のループに行く
  if (num % 2 === 0) continue
  console.log(num)
}

// 1
// 5
```

### 関数の戻り値が決められる
関数内で使う場合、その関数の戻り値を決めるために`return`が使えます。

例えば、以下の動作をする`sumEven`関数を楽に定義できます。

- 配列の要素が偶数の間合計し続ける
- 奇数があったらその時点の合計を返す

```js
function sumEven(array) {
  let sum = 0
  for (const num of array) {
    if (num % 2 !== 0) return sum // 奇数の場合
    sum += num
  }
  return sum // 全ての数が偶数だった場合
}
```

これを`forEach`で実装する場合、一手間必要になります。
一番簡単なのはフラグを用意する方法でしょうか。

```js
function sumEven(array) {
  let sum = 0
  let flag = false // 奇数の値がすでに出現したかどうか
  array.forEach(num => {
    if (flag) return // フラグがtrueなら何も足さない
    if (num % 2 !== 0) { // 奇数の場合
      flag = true
      return
    }
    sum += num
  })
  return sum // 全ての数が偶数だった場合
}
```

## できないこと
### インデックスを使うには一手間必要

`forof`文でインデックスを用いたループをしたい場合、配列の`entries`メソッドを使う必要があります。

```js
const array = ['A', 'B', 'C']
// (const [インデックスの変数名, 値の変数名] of 配列名.entries())
for (const [index, value] of array.entries()) {
  console.log(`No. ${index}: ${value}`)
}

// No. 0: A
// No. 1: B
// No. 2: C
```

配列の`entries`メソッドは、`[インデックス, 値]`という形式の値を持つ反復可能オブジェクトを返します。
詳細は[MDN](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Array/entries)をご覧ください。

## 制御文と`forEach`

### `return`と`continue`
`forEach`の`return`、`forof`の`continue`は、実質的に同じように動きます。

- `forEach`の`return`: 今の関数の実行を終わり、次のループに行く
- `forof`の`continue`: 今回のループを終わり、次のループに行く

### それ以外
それ以外の制御文は、`forEach`に対応するものはありません。

- `forof`の`break`: `forEach`ではできない
- 関数内での`forof`の`return`: `forEach`ではできない

`forEach`で`break`したくなった場合、**代わりに`forof`を使う**のが一番わかりやすくて良い選択肢でしょう。