JavaScript forEachを何度も調べがちな方々へ

# はじめに
この記事は`JavaScript`の`forEach`の仕様について書いています。
開発作業中に調べる頻度が高いと思ったので、一度しっかり理解しておこうと思い記事を書きました。同じような方は参考にしていただけますと幸いです。

公式ドキュメントを参考にして書いています。

https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Array/forEach


# forEachとは
念の為、`forEach`について確認しておきたいと思います。
>forEach() メソッドは反復処理メソッドです。指定された関数 callbackFn を配列に含まれる各要素に対して一度ずつ、昇順で呼び出します。 map() と異なり、 forEach() は常に undefined を返し、連鎖させることはできません。典型的な使用する用途は、チェーンの終わりで副次効果を実行することです。

上記は公式ドキュメントの一部を引用したものです。
配列の要素数の数だけループを回すメソッドで、返り値はありません。

下記のように配列に対してメソッドチェーンで繋げて使用します。
```js
const ary = [1, 2, 3]
ary.forEach((x) => {
  console.log(x)
})

// 出力結果
// 1ループ目： 1
// 2ループ目： 2
// 3ループ目： 3
```

# forEachの仕様
次の6つについての説明していきたいと思います。
1. ループを止める方法について
1. ループ対象に空の要素があるとスキップされる
1. ループ中にループ対象の要素数が増えた場合にどうなるか
1. ループ中にループ対象の要素を変更した場合にどうなるか
1. 非同期関数の終了を待たない
1. オブジェクトをループ対象とする場合はどうすればいいか

## ループを止める方法について
どうすればforEachのループが止まるかについて説明していきます。
まず結論ですが下記のようになります。
| アクション | ループが止まるかどうか |
| ---- | ---- |
| break | ✖️ |
| continue | ✖️ |
| return | ◯ |
| 例外処理発生 | ◯ |

forEachでは`break`や`continue`でループを止めることはできません。  
ループを止めるには`return`を使います。
```js
const ary = [1, 2, 3]
ary.forEach((x) => {
  if (x === 1) {
    return
  }
  console.log(x)
})

// 出力結果
// 1ループ目： 出力なし
// 2ループ目： 2
// 3ループ目： 3
```
`return`は`for`文でいうと`continue`のような動きにあたります。

それとループを止めるには下記のようにtry..catchを使わずに例外処理を発生させるという方法もあります。
```js
const ary = [1, 2, 3]
ary.forEach((x) => {
  if (true) {
    throw "Error Text"
  }
  console.log(x)
})
```
エラーメッセージ
```tex
Uncaught Error Text
```
この場合`forEach`だけでなく全プロセスが終了する可能性もあります。

## ループ対象に空の要素があるとスキップされる
「空」という言葉が少し曖昧なので説明させていただきますと、下記のような配列のことです。
```js
const ary = [1, , 3]
```
空文字や`undefined`のことではなく、配列に何も定義されていな状態のことです。
`forEach`を使って出力すると下記のような結果になります。
```js
const ary = [1, , 3]
ary.forEach((x) => {
  console.log(x)
})

// 出力結果
// 1ループ目： 1
// 2ループ目： スキップされる
// 3ループ目： 3
```
ちなみに`undefined`を設定するとスキップされずに出力されます。
```js
const ary = [1, undefined, 3]
ary.forEach((x) => {
  console.log(x)
})

// 出力結果
// 1ループ目： 1
// 2ループ目： undefined
// 3ループ目： 3
```

## ループ中にループ対象の要素数が増えた場合にどうなるか
`forEach`内でループ対象の配列に対して、要素を追加してもその分は
ループ対象には入りません。  
```js
const ary = [1, 2, 3]
ary.forEach((x) => {
  // 1ループ目に配列の要素を追加する
  if (x === 1) {
    ary.push(4)
  }
  console.log(x)
})

console.log(ary)

// 出力結果
// 1ループ目： 1
// 2ループ目： 2
// 3ループ目： 3
// [1, 2, 3, 4] ← ループの外のconsole.log()では要素が追加されている
```
出力結果を見ていただくと、配列に要素を追加しているにもかかわらず、3ループ目で`forEach`が終了していることがわかると思います。
これは`forEach`開始時点での要素数の分だけループを回すためです。  

## ループ中にループ対象の要素を変更した場合にどうなるか
下記のコードの通り、ループ前の要素であれば変更が適用されます。
```js
const ary = [1, 2, 3]
ary.forEach((x) => {
  // 1ループ目に配列の要素を変更する
  if (x === 1) {
    ary[2] = 4
  }
  console.log(x)
})

// 出力結果
// 1ループ目： 1
// 2ループ目： 2
// 3ループ目： 4
```
`forEach`は開始時点の配列の要素数のみを保存して、その要素の内容については特に保存していないようです。  

## 非同期関数の終了を待たない
`async`と`await`を使って非同期処理を同期的に実行しようとした際などに、`forEach`は非同期処理の終了を待ちません。  
```js
const ratings = [5, 4, 5]
let sum = 0
const sumFunction = async (a, b) => a + b
async function foo() {
  ratings.forEach(async (rating) => {
    sum = await sumFunction(sum, rating)
  })
  console.log(sum)
}
foo()

// 出力結果
// 0
```
上記のコードは公式ドキュメントから引用したものです。  
非同期関数の終了を待つ場合は結果が14になるはずですが、0が出力されてしまいます。  

https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Array/forEach#%E8%A7%A3%E8%AA%AC

ちなみにこの問題は`for`を使うことで解決できます。
```js
const ratings = [5, 4, 5]
let sum = 0
const sumFunction = async (a, b) => a + b
async function foo() {
  for (let i = 0; i < ratings.length; i++) {
    sum = await sumFunction(sum, ratings[i])
  }
  console.log(sum)
}
foo()

// 出力結果
// 14
```

## オブジェクトをループ対象とする場合
オブジェクトを`forEach`のループで回す方法について説明します。
```js
const obj = { length: 2, 0: 0, 1: 1 }
Array.prototype.forEach.call(obj, (x) => console.log(x))
// 出力結果
// 1プール目：0
// 2プール目：1
```
上記のように`Array.prototype.forEach.call`を使ってオブジェクトをループで回すことができます。オブジェクトには`length`キーが含まれている必要があり、そこに設定された数値の分だけループが繰り返されます。  
また、キー名は数値である必要があります。

オブジェクトをループで回すのに`forEach`は少し使い勝手が悪いと思いますので、他の方法がいいかと思います。

# 終わりに
最後まで読んでいただきありがとうございました。
普段使っているメソッドもしっかり調べると知らなかった仕様がたくさんあるもんだなと思いました。

# 参考

https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Array/forEach