JavaScript:配列の基本操作
1学習の目的
- 配列の作成・取り出し・追加・削除・切り出しができるようになる。変数を10個作る代わりに配列1本で扱えるようになる。
- 並べ替えと検索を正しく書けるようになる。とくに数値のソートがそのままでは壊れるという、JavaScript最大級の罠を回避できるようになる。
2基礎解説
| やりたいこと | 書き方 | 注意点 |
|---|---|---|
| 末尾に追加/削除 | push() / pop() | 元の配列を書き換える |
| 一部を切り出す | slice(1, 3) | 元は無傷。1以上3未満 |
| 含まれるか調べる | includes("値") | true / false を返す |
| 数値を並べ替える | sort((a, b) => a - b) | 比較関数が必須 |
- 番号は0始まり。末尾は items[items.length - 1] か items.at(-1)。個数は items.length(カッコ不要)。
- sort() をそのまま数値に使うと壊れる。文字列として比較されるため [10, 9, 100] が [10, 100, 9] になる。数値は必ず sort((a, b) => a - b) と書く。
- slice は元を変えずコピーを返す、splice は元を書き換える。名前が似ているが性質は正反対。
- sort reverse push は元の配列を変更する。元を残したいときは [...items].sort(…) のようにコピーしてから操作する。
- スプレッド構文 [...a, ...b] で連結やコピーができる。const copy = items; はコピーではなく同じ配列を指すので、片方を変えるともう片方も変わる。
現場使用例:APIから受け取った一覧の保持、ランキングの並べ替え、カートへの商品追加 cart.push(item)、上位3件の切り出し sorted.slice(0, 3)。
const items = ["りんご", "みかん", "ぶどう"];
console.log(items[0]); // りんご
console.log(items.at(-1)); // ぶどう(末尾)
console.log(items.length); // 3
items.push("もも"); // 末尾に追加
console.log(items); // ['りんご','みかん','ぶどう','もも']
console.log(items.slice(0, 2)); // ['りんご','みかん'] 元は無傷
console.log(items.includes("みかん")); // true
// 数値の並べ替えは比較関数が必須
const nums = [10, 9, 100, 1];
console.log([...nums].sort()); // [1, 10, 100, 9] ← 壊れる
console.log([...nums].sort((a, b) => a - b)); // [1, 9, 10, 100] ← 正しい
3基本ドリル(10問)
出力 == りんご / ぶどう(2行)
先頭は [0]、末尾は at(-1) で取り出せる。
const fruits = ["りんご", "みかん", "ぶどう"]; console.log(fruits[0]); console.log(fruits.at(-1));
at(-1) なら要素が何個でも末尾が取れる。fruits[fruits.length - 1] と書いても同じだが、こちらのほうが短い。
A. "b" B. "c" C. "d" D. エラー 選択★☆☆無料
解答 == B
番号は0から数える。
B
0番が "a"、1番が "b"、2番が "c"。「2番目」と「番号2」は違うという点が初学者最大の混乱ポイント。
出力 == [ 1, 2, 3, 4 ]
push() を使う。戻り値ではなく配列自体を出力する。
const nums = [1, 2, 3]; nums.push(4); console.log(nums);
push の戻り値は追加後の要素数なので、nums = nums.push(4) と書くと 4 という数字が入ってしまう。代入しないのが正解。
const colors = ["赤", "青", "黄"]; console.log(colors.____);
出力 == 3
個数を表すプロパティ。カッコは付けない。
length
length はプロパティなのでカッコ不要。文字列にも使えて文字数が取れる。メソッドと混同して length() と書くのがよくあるミス。
A. [20, 30] B. [20, 30, 40] C. [10, 20, 30] D. [30, 40] 選択★☆☆無料
解答 == A
開始は含む、終了は含まない。
A
番号1(20)から始まり、番号3(40)の手前まで。JS05の i < 5 と同じ「未満」の考え方。
出力 == [ 95, 88, 72, 61 ]
数値の降順は sort((a, b) => b - a)。比較関数を省略してはいけない。
const scores = [72, 95, 61, 88]; console.log(scores.sort((a, b) => b - a));
昇順は a - b、降順は b - a。この2つは丸暗記でよい。比較関数を省くと文字列として比較され、意図しない順序になる。
const items = ["りんご", "みかん", "ぶどう"];
console.log(items.____("みかん"));
出力 == true
「含む」を意味する英単語。true / false を返す。
includes
indexOf でも調べられるが「見つからないと -1」という戻り値を覚える必要がある。有無だけ知りたいなら includes のほうが読みやすい。
A. [1, 9, 10, 100] B. [1, 10, 100, 9] C. [100, 10, 9, 1] D. エラー 選択★★☆無料
解答 == B
比較関数がないとき、要素は何として比較されるか。
B
文字列として比較されるため "10" < "100" < "9" という並びになる。数値のソートで比較関数を忘れるのは、JavaScriptで最も有名なバグのひとつ。
出力 == 87 / 45(2行)
合計は let total = 0 で積み上げる。最大値は Math.max(...nums) とスプレッド構文で渡せる。
const nums = [12, 45, 7, 23];
let total = 0;
for (const n of nums) {
total += n;
}
console.log(total);
console.log(Math.max(...nums));
Math.max(nums) では配列そのものを渡してしまい NaN になる。スプレッド構文で展開して個々の数値として渡す必要がある。
出力 == [ 95, 88, 79 ]
[...scores] でコピーしてから sort し、slice(0, 3) で先頭3件を取る。
const scores = [72, 95, 61, 88, 79]; console.log([...scores].sort((a, b) => b - a).slice(0, 3));
並べ替え → 先頭を切り出すでランキング上位N件が1行で書ける。sort は元を壊すので、[...scores] のコピーを挟むのが安全な作法。
4実践シナリオ(5問)
3行の番号付きリスト+最終行 合計3点
追加は push。削除は indexOf で位置を調べてから splice(位置, 1)。出力は entries() で番号を振る。
const items = ["牛乳", "卵", "パン"];
items.push("バター");
items.splice(items.indexOf("卵"), 1);
for (const [i, item] of items.entries()) {
console.log(`${i + 1}. ${item}`);
}
console.log(`合計${items.length}点`);
splice(位置, 個数) は指定位置から指定数を削除する。値を指定して消す機能はないので、indexOf で位置を求めてから渡すのが定番の組み合わせ。
3行の成績+最終行 平均:81.3点
entries() の番号 i を使って scores[i] を引く。平均は toFixed(1) で小数1桁の文字列になる。
const names = ["佐藤", "鈴木", "高橋"];
const scores = [82, 95, 67];
for (const [i, name] of names.entries()) {
console.log(`${name}:${scores[i]}点`);
}
let total = 0;
for (const s of scores) {
total += s;
}
console.log(`平均:${(total / scores.length).toFixed(1)}点`);
2本の配列を同じ順番で対応させる「並行配列」。番号でひも付けるので順序がズレると壊れるのが弱点。次章のオブジェクトでこの弱点を解消する。
3行。1位:450万円 / 2位:340万円 / 3位:210万円
[...sales].sort((a, b) => b - a).slice(0, 3) で上位3件を作り、entries() で順位を振る。
const sales = [120, 340, 210, 450, 180];
const top3 = [...sales].sort((a, b) => b - a).slice(0, 3);
for (const [i, s] of top3.entries()) {
console.log(`${i + 1}位:${s}万円`);
}
D10の「上位N件」とJS05の entries() を組み合わせた形。ランキング表示はこの2手で必ず作れる。
3行。A:3件 / B:2件 / C:1件
空の配列を用意し、includes で「まだ入っていなければ push」する。件数は for...of で数えるか、絞り込んだ長さで求める。
const orders = ["A", "B", "A", "C", "B", "A"];
const unique = [];
for (const o of orders) {
if (!unique.includes(o)) {
unique.push(o);
}
}
for (const u of unique) {
let count = 0;
for (const o of orders) {
if (o === u) {
count++;
}
}
console.log(`${u}:${count}件`);
}
includes で重複を防ぐのは定番テクニック。ただしループが二重になっていて効率がよくない。次章のオブジェクトを使うと1回のループで書けるようになる。
「ソート後(コピー):[95,72,61]」(降順)
「元の配列:[72,95,61]」(変わっていないこと)
「連結:[72,95,61,100]」(スプレッド構文で 100 を足した新しい配列)
※配列は JSON.stringify() で文字列にして表示すること コーディング★★★無料
出力3行が完全一致
コピーは [...scores]。連結も [...scores, 100] で新しい配列が作れる。どちらも元の scores は変わらない。
const scores = [72, 95, 61];
const sorted = [...scores].sort((a, b) => b - a);
const added = [...scores, 100];
console.log(`ソート後(コピー):${JSON.stringify(sorted)}`);
console.log(`元の配列:${JSON.stringify(scores)}`);
console.log(`連結:${JSON.stringify(added)}`);
スプレッド構文はコピーを作る道具。const copy = scores; と書くと同じ配列を指すだけなので、copy を変更すると scores も変わってしまう。元データを守りたい場面では必ず [...] を挟む。
5仕上げ課題
const products = ["ボールペン", "ノート", "消しゴム", "ファイル", "付箋"];
const stocks = [120, 8, 45, 3, 67];
const threshold = 10;
仕様:
① 各商品を「1. ボールペン:120個」の形式で出力。在庫が threshold 未満の商品は末尾に「 ⚠発注」を付ける
② 明細の後に --- の行を出す
③ 総在庫と品目数を1行で出す
④ 要発注の品目数と商品名(読点区切り)を1行で出す
⑤ 在庫が多い順の上位3商品名を1行で出す(元の配列を壊さないこと)
出力仕様(10行):
=== 在庫レポート ===
1. ボールペン:120個
2. ノート:8個 ⚠発注
3. 消しゴム:45個
4. ファイル:3個 ⚠発注
5. 付箋:67個
---
総在庫:243個 / 品目数:5
要発注:2品目(ノート、ファイル)
在庫トップ3:ボールペン、付箋、消しゴム
出力10行が完全一致
entries() の番号 i で stocks[i] を引く。要発注の商品名は空配列に push で集め、join("、") でつなぐ。トップ3は [...stocks] をコピーして降順ソートし、上位3つの在庫数それぞれについて indexOf で products の名前を引く。
const products = ["ボールペン", "ノート", "消しゴム", "ファイル", "付箋"];
const stocks = [120, 8, 45, 3, 67];
const threshold = 10;
console.log("=== 在庫レポート ===");
const low = [];
let total = 0;
for (const [i, name] of products.entries()) {
const stock = stocks[i];
total += stock;
const mark = stock < threshold ? " ⚠発注" : "";
if (stock < threshold) {
low.push(name);
}
console.log(`${i + 1}. ${name}:${stock}個${mark}`);
}
const top3 = [...stocks].sort((a, b) => b - a).slice(0, 3);
const topNames = [];
for (const s of top3) {
topNames.push(products[stocks.indexOf(s)]);
}
console.log("---");
console.log(`総在庫:${total}個 / 品目数:${products.length}`);
console.log(`要発注:${low.length}品目(${low.join("、")})`);
console.log(`在庫トップ3:${topNames.join("、")}`);
2本の並行配列を番号でひも付けながら、1回のループで明細と集計を同時に作るのが要点。indexOf は「値から番号を逆引き」する道具で、ソート後の在庫数から商品名を復元するのに使っている。
そして⑤の「元の配列を壊さない」が最重要だ。stocks.sort() と書いてしまうと stocks 自体が並び替わり、その後の indexOf が別の商品を指してしまう。[...stocks] のコピーを挟むだけでこの事故は防げる。
ただしこのコードには限界もある。商品名と在庫数が別々の配列なので、片方だけ並べ替えると対応が崩れる。次章のオブジェクトを使えば、名前と在庫を1つにまとめて安全に扱えるようになる。