順番に読み進めながら学べます

繰り返し — for / for...of / for...in

JavaScript の繰り返しを基礎から学びます。回数を数える for、要素を順に受け取る for...of、キーをたどる for...in と、二重ループの組み立て方までを扱います。

注文された商品の金額を 1 行ずつ表示するとき、明細が 3 件なら 3 行、10 件なら 10 行と同じ形のコードを並べることになります。件数はデータによって変わるため、行数を先に決められません。

この記事では、回数を数えて繰り返す for、要素を順に受け取る for...of、キーをたどる for...in を扱います。

同じ処理を件数だけ繰り返す — for の 3 つの式

注文明細の品名を 1 行ずつ表示したいとします。console.log(orderLines[0]) から順に書き並べる方法では、明細が 4 件に増えた時点で行を足すことになり、件数が変わるたびにコードを直すことになります。

for(数えながら同じ処理を繰り返す構文)は、丸括弧にセミコロンで区切った 3 つの式を書き、繰り返したい処理は波括弧の中に書きます。この波括弧の中が本体です。3 つの式は左から初期化・条件・更新で、条件は本体に入る前に判定され、更新は本体を実行したあとに走ります。

i++i = i + 1 と同じ意味で、値が毎回書き換わるため i は let で宣言します。

const orderLines = ["マグカップ", "ノート", "ボールペン"];   // 注文明細の品名

// i を 0 から始め、件数に届くまで i++ で 1 ずつ増やす
for (let i = 0; i < orderLines.length; i++) {
  // index は 0 から始まるので、表示は i + 1 で 1 件目から数える
  console.log(`${i + 1} 件目: ${orderLines[i]}`);
}
// 1 件目: マグカップ
// 2 件目: ノート
// 3 件目: ボールペン

console.log(`表示した件数: ${orderLines.length}`);   // 表示した件数: 3
for の 1 周分の動き
初期化let i = 0条件i < 3本体1 件表示更新i++ループ終了成立再判定不成立

初期化は最初の 1 回だけ、条件は本体に入る前、更新は本体のあとに実行されます。条件が成り立たなくなった回は、本体を実行せずにループが終わります

条件の判定から本体、更新までをまとめて 1 周と数えます。更新が本体のあとに走るので、i の値はそれまでに本体を実行し終えた回数と一致し、1 周目の本体は i が 0 のまま実行されます。

下の表は周ごとの i と判定です。

周回i の値と条件本体で起きること
1 周目i = 0 / 0 < 3 が成立1 件目: マグカップ
2 周目i = 1 / 1 < 3 が成立2 件目: ノート
3 周目i = 2 / 2 < 3 が成立3 件目: ボールペン
4 周目の入口i = 3 / 3 < 3 は不成立本体は動かずループ終了

配列の外を読むと undefined になります

条件を i <= orderLines.length と書くと最後にもう 1 周増え、存在しない orderLines[3] を読みます。範囲外は undefined なので「4 件目: undefined」と表示され、金額を合計する処理なら結果が NaN になります。条件は件数より小さい間で止めます

カートに入っている商品の金額を集計します。金額の配列 unitPrices は宣言済みです。

① 合計を入れる変数を 0 で用意してください。

② for で数える変数を 0 から順に動かし、合計に 1 件ずつ足してください。

③ 集計した合計を「合計: ◯◯ 円」の形で表示してください。

④ 件数で割った平均を、小数を切り捨てて「平均: ◯◯ 円」の形で表示してください。

(正しく実行できれば解説が表示されます)

JavaScript / TypeScript エディタ

コードを実行してください

要素をそのまま受け取る — for...of と entries

発送待ちの注文番号を順に処理したいとします。for で書くと、index の変数を宣言し、条件と更新を書いたうえで orderIds[i] と取り出すことになり、index を書き間違えると別の要素を読んでしまいます。

for...of(配列などから要素を 1 つずつ取り出して変数に入れる構文)は for (const orderId of orderIds) と書き、丸括弧には宣言と配列だけを置きます。

番号も要るときは orderIds.entries() を回し、周ごとに渡る [0, "A-1001"] のような組を [index, orderId] と角括弧で受け取ります。

const orderIds = ["A-1001", "A-1002", "A-1003"];   // 発送待ちの注文番号

// 要素そのものが orderId に入る(index は出てこない)
for (const orderId of orderIds) {
  console.log(`発送待ち: ${orderId}`);
}
// 発送待ち: A-1001 …(3 行)

// index も要るときは entries() で組にして受け取る
for (const [index, orderId] of orderIds.entries()) {
  console.log(`${index + 1} 番目: ${orderId}`);
}
// 1 番目: A-1001
// 2 番目: A-1002
// 3 番目: A-1003
受け取り方変数に入るもの表示される行
of で要素を受ける要素そのもの(A-1001)発送待ち: A-1001
entries() を [index, orderId] で受けるindex は 0 / orderId は A-10011 番目: A-1001
entries() を変数 1 つで受けるindex と要素の組がそのまま[0, "A-1001"] の配列のまま

const [index, orderId] は分割代入の記事で扱った配列の受け取り方と同じで、1 つ目の位置に index、2 つ目に要素が入ります。周ごとに新しい変数が作られるので、for...of のループ変数は const で宣言しても再代入のエラーになりません。

配送状況の一覧から、まだ届いていない注文を探します。注文番号 id と状態 status を持つ注文の配列 deliveries は宣言済みです。

① for...of で注文を 1 件ずつ受け取り、「A-1001: 配達済み」の形で表示してください。

② 位置も一緒に受け取る形で回し直し、配達済みでない注文だけを「2 件目 A-1002: 配達中」の形で表示してください。

③ ② で表示した件数を数え、最後に「未着 ◯ 件」と表示してください。

JavaScript / TypeScript エディタ

コードを実行してください

キーをたどる — for...in と配列で使わない理由

会員プロフィールのように、キーの名前と値の両方を並べて表示したいことがあります。Object.keys(profile) で配列にしてから for...of で回すこともできますが、キーをたどるだけならもう 1 つ短い書き方があります。

for...in(オブジェクトのキーを 1 つずつ取り出す構文)は for (const key in profile) と書きます。変数に入るのは値ではなくキーの文字列なので、値は profile[key] のようにブラケット記法で読み出します。

const profile = { displayName: "田中", plan: "premium", posts: 12 };

// key にはキーの文字列が入る(値ではない)
for (const key in profile) {
  console.log(`${key}: ${profile[key]}`);
}
// displayName: 田中
// plan: premium
// posts: 12

// 配列に使うと、index が文字列で渡ってくる
const stockCounts = [5, 0, 12];
for (const key in stockCounts) {
  console.log(`${typeof key} / ${key + 1}`);
}
// string / 01
// string / 11
// string / 21   ← 数値ではなく連結される
for...in が渡すのはキー
オブジェクトprofilekey はキーの文字列profile[key]で値を読むplan: premium配列stockCountskey は"0" "1" "2"key + 1 は文字列の連結"01" になる
オブジェクトでもキーの文字列、配列でも index の文字列が渡ります。配列で 1 を足すと数値の計算にならず、文字列の連結になります

stockCounts[key] のように要素を読むだけなら、文字列の "1" でも値を取り出せます。Number(key) で変換すれば計算にも使えますが、配列の要素を扱うなら最初から別の書き方を選べます。下の表は 3 つの書き方の使い分けです。

書き方変数に入るもの向いている場面
for (let i = 0; i < 件数; i++)index の数値番号を計算に使いたいとき
for (const item of 配列)要素そのもの要素だけを順に処理するとき
for (const key in オブジェクト)キーの文字列オブジェクトのキーをたどるとき

オブジェクトは for...of では回せません

for (const item of profile) のようにオブジェクトを of の右に置くと、TypeError: profile is not iterable で止まります。オブジェクトは要素を順に渡す仕組みを持たないためです。値まで順に扱うなら、Object.entries で配列にしてから渡します

プロフィールの編集画面で、保存前と保存後を比べて変わった項目だけを表示します。変更前の before と変更後の after は宣言済みです。

① 変わった項目を数える変数を 0 で用意してください。

② for...in で変更前のキーを 1 つずつ取り出してください。

③ 同じキーで変更後の値も読み、値が違う項目だけ「city: 横浜 → 川崎」の形で表示して数えてください。

④ 最後に「変更 ◯ 項目」と表示してください。

JavaScript / TypeScript エディタ

コードを実行してください

組み合わせを一覧にする — 二重ループの内と外

店舗ごとの取り扱い商品を、店舗と商品の組み合わせで一覧にしたいとします。ループを 1 つ書くだけでは、店舗を 1 件ずつ進めるか商品を 1 件ずつ進めるかのどちらかにしかならず、行と列の両方を動かせません。店舗の数も商品の数もデータ次第で変わります。

ループの本体には、別のループをそのまま置けます。外側が 1 周する間に内側が最後まで回り切るので、内側の本体が実行される回数は外側の回数と内側の回数の掛け算です。内側からは外側のループ変数もそのまま読めます。

const stores = ["渋谷店", "横浜店"];
const products = ["マグカップ", "ノート"];

// 外側が 1 周する間に、内側が最後まで回る
for (const store of stores) {
  for (const product of products) {
    console.log(`${store} / ${product}`);
  }
}
// 渋谷店 / マグカップ
// 渋谷店 / ノート
// 横浜店 / マグカップ
// 横浜店 / ノート
内側のループから読める変数
外側の for...of(stores を 1 周ずつ)
  • store — 1 周ごとに "渋谷店" → "横浜店" と入れ替わる
  • 外側の本体は 2 回だけ実行される
内側の for...of(products を最後まで)
  • product — 内側の 1 周ごとに "マグカップ" → "ノート"
  • 外側の store もそのまま読める
  • この本体だけが 2 × 2 の 4 回実行される
内側の本体からは、内側の product と外側の store の両方を読めます。内側で宣言した product が使えるのは、内側の波括弧の中だけです。

内側の for...of は外側の周が変わるたびに products の先頭から回り直すので、store が同じ間は product だけが入れ替わります。外側の本体の先頭で宣言した変数も、外側の周が変わるたびに作り直されます。波括弧ごとに変数の使える範囲が決まる規則は、スコープの記事で扱います。

注文ごとに明細の金額を合計します。番号 id と明細の金額の配列 prices を持つ注文の配列 orders は宣言済みです。

① 外側のループで注文を 1 件ずつ取り出してください。

② 注文ごとの小計を入れる変数を、注文が変わるたびに 0 から始まるように用意してください。

③ 内側のループでその注文の金額を 1 件ずつ取り出し、小計に足してください。

④ 内側のループのあとに「A-1001: 2 点 / 1760 円」の形で表示してください。

JavaScript / TypeScript エディタ

コードを実行してください
QUIZ

理解度チェック

まずは1問ずつ答えてみましょう。

Q1orderIds.entries()for...of で回したとき、1 周目の index はどうなりますか?

Q2配列を for...in で回したとき、ループ変数に入るのはどれですか?

Q33 件の配列と 4 件の配列を二重ループで回すと、内側の本体は何回実行されますか?