Q1orderIds.entries() を for...of で回したとき、1 周目の index はどうなりますか?
繰り返し — 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
初期化は最初の 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 になります。条件は件数より小さい間で止めます。
要素をそのまま受け取る — 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-1001 | 1 番目: A-1001 |
| entries() を変数 1 つで受ける | index と要素の組がそのまま | [0, "A-1001"] の配列のまま |
const [index, orderId] は分割代入の記事で扱った配列の受け取り方と同じで、1 つ目の位置に index、2 つ目に要素が入ります。周ごとに新しい変数が作られるので、for...of のループ変数は const で宣言しても再代入のエラーになりません。
キーをたどる — 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 ← 数値ではなく連結される
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 で配列にしてから渡します。
組み合わせを一覧にする — 二重ループの内と外
店舗ごとの取り扱い商品を、店舗と商品の組み合わせで一覧にしたいとします。ループを 1 つ書くだけでは、店舗を 1 件ずつ進めるか商品を 1 件ずつ進めるかのどちらかにしかならず、行と列の両方を動かせません。店舗の数も商品の数もデータ次第で変わります。
ループの本体には、別のループをそのまま置けます。外側が 1 周する間に内側が最後まで回り切るので、内側の本体が実行される回数は外側の回数と内側の回数の掛け算です。内側からは外側のループ変数もそのまま読めます。
const stores = ["渋谷店", "横浜店"];
const products = ["マグカップ", "ノート"];
// 外側が 1 周する間に、内側が最後まで回る
for (const store of stores) {
for (const product of products) {
console.log(`${store} / ${product}`);
}
}
// 渋谷店 / マグカップ
// 渋谷店 / ノート
// 横浜店 / マグカップ
// 横浜店 / ノート
store— 1 周ごとに "渋谷店" → "横浜店" と入れ替わる- 外側の本体は 2 回だけ実行される
product— 内側の 1 周ごとに "マグカップ" → "ノート"- 外側の
storeもそのまま読める - この本体だけが 2 × 2 の 4 回実行される
product と外側の store の両方を読めます。内側で宣言した product が使えるのは、内側の波括弧の中だけです。内側の for...of は外側の周が変わるたびに products の先頭から回り直すので、store が同じ間は product だけが入れ替わります。外側の本体の先頭で宣言した変数も、外側の周が変わるたびに作り直されます。波括弧ごとに変数の使える範囲が決まる規則は、スコープの記事で扱います。
理解度チェック
まずは1問ずつ答えてみましょう。
Q2配列を for...in で回したとき、ループ変数に入るのはどれですか?
Q33 件の配列と 4 件の配列を二重ループで回すと、内側の本体は何回実行されますか?