◆ 関数不要
◆ break, continue, return を使い分けられる
◆ 配列以外にもそのまま使える

for-of の時代

昔は for 文なんてダメ 全部 forEach にすべき なんて言っていましたが最近は for-of です

for-of は iterable な値に対して各要素をループで実行してくれるものです
いわゆる foreach 構文です
使えるようになったのはもう結構前で ES2015 の最初からです

forEach だった理由

これまでなんで forEach 1 択だったのかというと 他の for や for-in が色々ダメだったからです

柔軟にループ変数を制御する必要のある特別なものでは for や while を使うことになりますが 大抵はシンプルなコレクション要素に対してループしたいというものです
配列や NodeList や arguments などなどです

これらに for を使うとコード量は増えますし ちょっと書き間違って無限ループする可能性がなくもないです
また 読みやすさでもわかりづらいです
ループ変数を使って値を取り出したり 手続き型っぽい動きで順番に処理するので丁寧に見ていかないとやりたいことがわかりづらいです
パット見て単純に全部の要素に対して実行してるのか ループ変数を更新したりしなかったりなど特別なことしてるのかもわかりません

forEach メソッドだと各要素を引数にした関数を実行してくれるので for 文のめんどくささとわかりづらさがなくなります

なにより JavaScript は最近まで関数スコープだったので ループ処理にスコープを作れるというのが良かったです
スコープにほんの一箇所しか使わない変数がいっぱいあると読みづらさと間違いやすさが増しますからね


読みやすさだけでなく 動きの面でも困ったことがありました
ループの中でイベントリスナを設定するなど 内側で関数を作ってその中でループ変数を参照した場合 実行されるときの値が使われるのでループ中の値と実行時の値が異なります

例えば 5 回ループを回す場合 各ループのときのループ変数は 0 ~ 4 です
一般的な for 文で書いた場合ループを抜けるときにループ変数が 5 になってループ条件を満たさなくなります
関数が実行されるときはこの 5 になっているので存在しないインデックスにアクセスしてエラーということが多いです

forEach メソッドを使っておけば別スコープになってループ中の値がそのまま実行時の値になり そういった心配もなくなります
さらには 関数を渡すので同じ処理なら関数を別のところで定義しておいて渡すことで使いまわせます
いいことばかりです


デメリットをあえて上げるなら 「function(){}」 という宣言文を書くのが面倒だったのと関数実行なのでパフォーマンス面でちょっと for に負けること あと this 問題です
パフォーマンスが劣るとは言っても JavaScript って比較的関数型言語風で関数呼び出しのコストはかなり低めです

PHP だと配列をイテレーションするときに コールバックを受け取る関数より foreach 使うと格段に早いからと foreach にすべきだというのも目にします
実際 PHP で再帰関数とか関数呼び出しが多くなる処理するとき V8JS を使ってそこだけ JavaScript で実行したほうが早かったりしましたし

「function」 って書くのが面倒なのと this 問題は ES2015 でアロー関数が導入されて解決しました
「.forEach(x => x.remove())」 のように短く書けて this も変更されず外側の this を参照できます

for-of

色々メリットがあって ES2015 でさらに使いやすくなった forEach なので しばらくは forEach を使い続けていました
ですが 最近は for-of 使うことがメインです

for-of のメリットはこんなものがあります

  • 関数不要
  • iterable なオブジェクトならなんでも使える
  • break, continue, return の 3 種類の制御ができる


for 文の一種なのでコールバック関数は使いません
関数にするメリットがスコープだったのですが ES2015 から const でブロックスコープが作れます
そのおかげで 関数をあえて作る必要がなくなりました

再帰関数にしたいとか 使いまわしたいという場合は まだ .forEach が優秀ですが 繰り返しの処理の大半ってその場専用の処理で再帰関数にすることもめったにないと思います


forEach は配列のメソッドです
[].forEach.call を使ったり配列化するなどすれば配列風オブジェクトにも使えますが 手間が増えますしムダに長くなって見づらいです
プロトタイプを強制的につなぎ直して NodeList のインスタンスが Array を継承したような動きをさせることは出来ます
たまにしていたりもしたのですが やっぱり組み込みオブジェクトのプロトタイプチェーンを変えるのはあまり気軽にしたくない部分でもあります

for-of なら 文字列でも NodeList でも iterable なものならなんでもループ処理可能です
自作オブジェクトで好きにイテレーションの処理をカスタマイズすることもできます


関数でなくなったことで地味なようで結構大きなメリットが break と continue が使えることです
forEach メソッドでは各要素に対して関数を適用します
関数なので途中でやめることはできます
しかしこれは continue に当たるもので break や return にあたるものはありません

ループのそれ以降を実行したくなかったり ループ処理を行っている関数自体の返り値としてこの値を返したいというときには ちょっと工夫が必要であまり見やすいとは言えないものでした
for-of ではただの for 文なので 途中でループを抜けたり関数自体を抜けてしまうこともできます

個人的に特に助かるのがネストした forEach 処理で一気に抜けたい時です

const values = []
let remain = 100

!function(){
for(const a of b){
for(const c of d){
if(c.e){
for(const f of g){
if(f.m === "n"){
values.push(f.o)
if(--remain === 0) return
}
}
}else{
if(c.i >= 0){
for(const j of k){
values.push(j.l)
if(--remain === 0) return
}
}else{
values.push(c.x)
if(--remain === 0) return
}
}
}
}
}()

console.log(values)

分岐と繰り返しがネストすることはけっこうあります
今回は条件を満たしたら結果のリストに要素を追加していって一定数になったら抜けるものです
for の途中なので簡単には抜けられません

各 for で抜ける状態かのフラグをチェックするとか 毎日のように JavaScript を日常的に見ていても年に片手で数える程度しか目にしなくてそんな機能あったっけというレベルの認識のラベル機能を使うことはしたくないです

そんなときにループをすべて for-of で書いていれば 条件を満たしたら抜けたいブロックを無名関数にして実行して 条件を満たしたら return することで対応できます
forEach メソッドで書いていると return で戻れず try-catch 使うとかあまり気の進まない方法に頼ることになっていましたが for-of + return でいい感じに出来ました


例を書いていて generator を使って 条件を満たしたら yield でそれぞれの値を返して 必要な一定数分を next で取得したほうがキレイかなと思ったりもしましたが まぁこのままにします

for-in

一応昔からある for-of っぽいものです
オブジェクトに対して key を対象にループ処理します

in に指定したオブジェクトと key から自分で値を取り出さないといけなかったり プロトタイプチェーンをたどって key を取得するから自身のプロパティか確認が必要など残念な部分が多いです

プロトタイプチェーンを辿った上で key が必要というレアケースならとても便利なメソッドです
でもそんなことはめったになくて基本はそのオブジェクトが持ってるキーが必要です
なので hasOwnProperty が必須みたいなものですが ライブラリなのにそれを理解せず 直接 for-in だけを使っているのも多くあります
ライブラリは実装するのを便利にしてくれるはずのものなのにライブラリのせいでできることが制限されることもあったりで 二重の意味で for-in は嫌いです