この記事をおすすめしたい人
- JavaScriptのforEachでbreakしたい人
- JavaScriptのforやforEachの挙動をしっかりと知りたい人
- つまりオレ
今回はJavaScriptのforやforEachで、途中でループを抜ける方法を調べます。
他に僕が書いている言語の場合、PHPなら「break」、.NETなら「Exit For」でループを抜けることができますが、どうもJavaScriptでは意図した挙動になっていないような気がします。
答えはもう見つかっているんですが、この際にきっちりと自分で挙動を確認してメモしておきます。
前提条件とか(読まなくていいです)
僕は素人プログラマでして、その中でもJavaScriptはぶっちぎりで素人です。
プロ級の素人です。
このサイトでNuxt.jsの勉強をしたので今でこそ作りたいものが作れるようになりましたが、あくまでNuxt.js限定で、HTMLに埋め込むJavaScriptはいまだによく分かりません。
そんなレベルの人間の実験なので、
おいおい、そんな知識で大丈夫か。
と呆れられること請け合いですが、
大丈夫だ、問題ない!
あ、Nuxt.jsで書いているので行末のセミコロンを忘れてますが、必要な人は書き足してくださいね。
JavaScriptのforEachが書きやす過ぎる
まずfor文の書き方↓。
const data = [1,2,3,4,5]
for (let i = 0; i < data.length; i++) {
//ここで処理
}
これに対してforEachの書き方↓。
const data = [1,2,3,4,5]
data.forEach(value => {
//ここで処理
})
配列に対して直感的に記述できて、なおかつ無駄な変数が登場しないので、
感覚的にも、記述量的にも、非常に書きやすいです。
なので単純に一定回数のループを回す場合や、配列のインデックスが必要な場合を除いて、僕はforEachを乱用と言ってもいいほど多用しています。
雑にループを回しても処理が遅くなった気がしない
昔、Perlで1000行くらいのCSVのデータを処理していた時は雑にループを回すと、
処理おっせーな…。
と感じることが多かったんですが、今はハードの進化なのか、ループのさせ方を考えたり、回数を減らしたり、不要になったら途中で抜けたりとチューニングをしなくても、体感0秒で処理が終わっています。
このせいで、めっちゃテキトーにループを回してしまっています。 1万行のループでもbreakせずに最後まで回しきってしまっていたりします。
こんな状況で開発をしているので、今回の目標はforEachでループの制御を意図した通りに行えることです。
for文の制御
この↓形を基本に、for文の挙動を見ていきます。
const data = [1,2,3,4,5]
for (let i = 0; i < data.length; i++) {
//ここで処理
}
ループを抜ける
この↓ようにbreakを使って、ループを抜けてみます。
const data = [1,2,3,4,5]
for (let i = 0; i < data.length; i++) {
console.log(data[i])
if ( data[i] == 3 ) {
break
}
}
ループの確認として、配列の要素の中身をコンソールに出力しています。
出力結果↓。
1
2
3
ちゃんと意図したタイミングでループを抜けているようです。
もうひとつ、returnを使ったコード↓。
const data = [1,2,3,4,5]
for (let i = 0; i < data.length; i++) {
console.log(data[i])
if ( data[i] == 3 ) {
return
}
}
出力結果↓。
1
2
3
//以下エラー
ループを抜けるには抜けたんですが、ループを抜けたのではなくて、ひとつ上のレベルにreturnがかかり、戻り値が空だったのでエラーが出てしまいました。
つまり、for文を抜けるのはbreakでおk!
ループを飛ばす
この↓ようにcontinueを使って、ループを飛ばしてみます。
const data = [1,2,3,4,5]
for (let i = 0; i < data.length; i++) {
if ( data[i] == 3 ) {
continue
}
console.log(data[i])
}
出力はif文の後に回しました。
出力結果↓。
1
2
4
5
ちゃんと3だけ飛ばされています。
for文でループを飛ばすのはcontinue!
まぁでもこれあんまり意味ないですよね。 ループ自体はきっちり回っているので。
使うとしたら、ループ内の処理が煩雑で、処理しないケースを早々に除外して可読性やメンテナンス性を上げたい場合とかでしょうか。
forEachの制御
この↓形を基本に、forEachの挙動を見ていきます。
const data = [1,2,3,4,5]
data.forEach(value => {
//ここで処理
})
ループを抜ける
まずはfor文同様にbreakを試してみます。
const data = [1,2,3,4,5]
data.forEach(value => {
if ( value == 3 ) {
break
}
console.log(value)
})
出力結果↓。
Syntax Error: Unsyntactic break
処理がどうの以前に記述エラーが出てしまいました。 おそらくforEachがループ処理ではないからでしょう。
次はreturn↓のパターン。
const data = [1,2,3,4,5]
data.forEach(value => {
if ( value == 3 ) {
return
}
console.log(value)
})
出力結果↓。
1
2
4
5
おっと、continueと同等の結果になってしまいました。
一応、この↓ように
const data = [1,2,3,4,5]
data.forEach(value => {
if ( value >= 3 ) {
return
}
console.log(value)
})
値の評価を変えることで擬似的にループを抜けたような処理に見せることができますが、出力の順序を変えると、
const data = [1,2,3,4,5]
data.forEach(value => {
console.log(value)
if ( value >= 3 ) {
return
}
})
出力結果↓。
1
2
3
4
5
ループが回っていないわけではなくて、処理を行っていないだけです。
5回のループならたいした問題ではないですが、1万回ループのうちの3回目で抜けたい場合は無駄ループがかなりの量になってしまいます。
ところがこのforEachでループを抜ける方法がない…。
ループを飛ばす
上で出てきたように、returnを使うとループを飛ばせます。
const data = [1,2,3,4,5]
data.forEach(value => {
if ( value == 3 ) {
return
}
console.log(value)
})
えー、どうしてこんなことになるかと言うとですね。
JavaScriptのforEachが制御文ではなくて、関数だからですね。 MDNでもArrayのメソッドだと書いてあります。
関数だからループ文ではないのでbreakできないし、returnすれば値が返るわけです。
じゃーどうすればいいのかというと、似た機能でbreakできる別の関数を使えばいいんです。
forEachでbreakしたいならfind()を使え!
MDNに注意書きがありました。
例外を発生する以外の方法で、forEach() ループを止めることはできません。ループ中に中断する必要がある場合、forEach() メソッドは適切な方法ではありません。
早期終了を行うには下記のような手段が適しています。
で、for文の他に4つの方法が紹介されていたので、それぞれの挙動を見ていきましょう。
Array.prototype.every()
every()はこういう↓関数だそうです。
every() メソッドは、列内のすべての要素が指定された関数で実装されたテストに合格するかどうかをテストします。 これは論理値を返します。
配列内の全ての要素が条件に合致するかどうかをチェックするようなので、ループ内でfalseを返した時にループが終了するのではないかと思われます。
実際にやってみましょう。
const data = [1,2,3,4,5]
const result = data.every(value => {
console.log(value)
if ( value == 3 ) {
return false
}
})
出力結果↓。
1
1回でループが終わってしまいました。
これはおそらく、値を返さなかった場合がfalseとして判定されているせいかと思われます。
なのでデフォルトでtrueを返す↓ようにすると、
const data = [1,2,3,4,5]
const result = data.every(value => {
console.log(value)
if ( value == 3 ) {
return false
}
return true
})
出力結果↓。
1
2
3
無事にループを抜けられました。
every()でもforEach+breakみたいな処理が可能ですが、この場合、resultにはfalseが返っているわけで、ちょっと用途的に違うかなーと思います。
Array.prototype.some()
some()はこういう↓関数だそうです。
some() メソッドは、配列の少なくとも一つの要素が、指定された関数で実装されたテストに合格するかどうかをテストします。 これはブール値を返します。
every()の逆バージョンというか、ループ内でtrueを返すとそこでループを抜けられそうです。
const data = [1,2,3,4,5]
const result = data.some(value => {
console.log(value)
if ( value == 3 ) {
return true
}
})
出力結果↓。
1
2
3
無事にループを抜けられました。
some()の用途的にも、これはかなりforEach+break的な動作ですね。 この場合、resultにはtrueが返ります。
ただ、some()は条件に合致する要素があったかどうかを返すだけなので、合致した配列のインデックスや中身を後から使う場合は、別に変数を用意してループ内で代入しておかないといけません。
Array.prototype.find()
find()はこういう↓関数だそうです。
find() メソッドは、提供されたテスト関数を満たす配列内の 最初の要素 の 値 を返します。
このfind()という関数、idやnameが一致した要素を取り出すのにけっこう使ってたんですが、よく考えたらこれがまさしくforEach+breakの動作ですね。
実際に動かしてみます。
const data = [1,2,3,4,5]
const result = data.find(value => {
console.log(value)
if ( value == 3 ) {
return true
}
})
出力結果↓。
1
2
3
無事にループを抜けられました。
この場合、resultには3が入ります。
僕が思っていたforEach+breakの動作はこれですね。
条件に一致したらループを終えて、合致した要素の内容を返ります。
Array.prototype.findIndex()
findIndex()はこういう↓関数だそうです。
findIndex() メソッドは、配列内の指定されたテスト関数に合格する最初の要素の位置を返します。 テスト関数に合格する要素がない場合を含め、それ以外の場合は -1 を返します。
find()と同等の機能で、欲しいのが配列の要素ではなくて、そのインデックスの場合ですね。
実際に動かしてみます。
const data = [1,2,3,4,5]
const result = data.findIndex(value => {
console.log(value)
if ( value == 3 ) {
return true
}
})
出力結果↓。
1
2
3
無事にループを抜けられました。
この場合、resultには値「3」のインデックス「2」が入ります。
以上をまとめると、
- ループを抜けたいだけなら、forEach()の代わりにevery()、some()、find()、findIndex()の全てが使える
- every()は配列の全ての要素が条件に合致するかをチェック
- some()は配列の中に条件に合致する要素があるかどうかをチェック
- find()は配列の中の条件に合致した最初の要素を得る
- findIndex()は配列の中の条件に合致した最初の要素のインデックスを得る
となります。
これは人によって…と言うか、用途によってどれが適しているか変わってきそうですが、まぁfind()が一番forEach+breakっぽい動作ですかね。
ゲームのサイトを作ってて「火属性のスキルを持っているキャラかどうか」みたいなのをよく使うんですが、この場合はスキルの配列に対してsome()が合ってそう。
まとめ
forEachでbreakしたいならfind()を使え!
ループを抜けたいタイミングで「return true」するだけです。
戻り値として条件に合致した配列の最初の要素が返ります。
以上、WordPressからお届けしました!