【Javascript】 for文とその亜種
ここでは、Javascriptのループについて紹介します。よくあるfor文だけでなく、全件走査するforEach、for-of、mapなども紹介します。
単純ループ
普通のfor文です。
const a = [1, 2, 3]; for (let i = 0; i < a.length; i++) { // ... }
配列の要素数は.length
で取得できます。
しかし、全件操作する場合には推奨されません。これは、
.length
する対象を間違える可能性がある- ネストした際に、インデックスの変数名がかぶる可能性がある
- 書き方が冗長
- 全件走査か部分的に見るのか、新しい配列を作るのかなど、役割や対象範囲がわかりにくい
という問題があります。部分的に走らせる、1個のfor文で複数の配列が関わるなど、特殊なパターン以外で用いるべきではありません。
for文を使ったforEach
for-in
とfor-of
の2種類あります。
const a = ["a", "b", "c"]; for (const i in a) { console.log(i); } for (const v of a) { console.log(v); }
0 1 2 a b c
for (let var in array)
のように書きます。
for-in
では、変数にインデックスが順番に入ります。そのため、配列の要素には普通にそのインデックスを指定してアクセスします。
for-of
では、変数に値が順番に入ります。そのためインデックスは取れませんが、インデックスが不要な場合は[index]
を書かなくて良いので簡略化できます。
配列を走査するメソッド
forEach
JavascriptでforEachというとこちらです。他言語ではfor-in
をforEachと表現することがありますね。
const a = ["a", "b", "c"]; a.forEach((v, i) => { console.log(i); console.log(v); });
0 a 1 b 2 c
forEach
では、引数にコールバック関数を指定します。
コールバック関数は, (value, index, array)
を仮引数に取ります。array
はもととなる配列です。通常はしなくてもスコープ内なので使いません。
第1引数がvalueである点に注意しましょう。
このforEachには戻り値がないため、すべての要素を利用した汎用的な処理を行う際に利用します。
map
配列の要素に同じ操作を加えることで、新しい配列を作る操作ができます。
const a = [3, 1, 9, 6, 6]; const b = a.map((v) => v * 2); console.log(b);
[ 6, 2, 18, 12, 12 ]
コールバックの引数は配列の値が入ります。戻り値は、新しい配列に入れる値です。
map
は元の配列の値を変えません。 (非破壊)
filter
filterでは、条件に合う値だけを取り出した配列を作ることができます。
const a = [10, 100, 30, 600, 500, 400, 1, 0, 1, 10]; const b = a.filter((v) => v > 100); console.log(b);
[ 600, 500, 400 ]
filter
に入れた関数は戻り値を真偽値で返し、true
だった要素だけを残した配列を作成します。
find
要素の中から特定の要素を1件探したい場合、find
を使うことが多いです。
複数件取得したい場合は、先ほど紹介したfilter
の方が適しています。
複数件マッチした場合、最初の要素のみ取得されます。
const a = [ {x: 10, y: 20}, {x: 10, y: 30}, {x: 20, y: 40}, {x: 20, y: 50}, ]; const b = a.find((v) => v.x == 10); console.log(b);
{ x: 10, y: 20 }
見つからなかった場合、undefined
が返ります。
reduce
合計値を計算する、文字列を加工しながら結合するなどに利用します。
また、決まった複雑な形式で連想配列に変換したい場合にも利用する場合があります。
const a = [1, 5, 4, 2]; const sum = a.reduce( (acc, v) => acc + v, 0 ); console.log(sum);
12
reduce
に入れるコールバック関数の第1引数は、累積している値が入ります。第2引数は配列のそれぞれの値です。
reduce
の第2引数には初期値を入れます。デフォルト値は0です。
acc
の動作を見てみるとこの様になっています。
const a = [1, 5, 4, 2]; const sum = a.reduce( (acc, v) => { console.log(acc); return acc + v; }, 0 );
0 1 6 10
配列を連想配列にしたい場合、スプレッド構文を用いて書くと比較的簡潔に書けます。
const a = [["foo", 1], ["bar", 20], ["hoge", 0.3]]; const dict = a.reduce( (acc, [k, v]) => ({...acc, [k]: v}), {} ); console.log(dict);
{foo: 1, bar: 20, hoge: 0.3}
スプレッド構文を用いて、前のオブジェクト+今回の値 としたオブジェクトを作ります。
しかし、動きを見ると計算量がO(n^2)であることがわかるため、パフォーマンスはあまり良くありません。 (0個の要素コピー+新要素 -> 1個の要素コピー+新要素 -> 2個の… -> 100個の… となるため)
シンプルに連想配列にするだけなら、Object.fromEntries
を使うべきです。
Object.fromEntries([["foo", 1], ["bar", 20], ["hoge", 0.3]])
とすると、上の例と同じ結果が得られます。
これでまかないきれないものはreduce
か、要素数が多いなら普通にfor-in
などをすると良いと思います。
まとめ
配列を全件走査する場合、普通のfor文は普通は用いません。
状況に応じてmap
やfilter
, forEach
を駆使してコードを書いていくと、見晴らしの良いコードがかけると思います。
