JavaScriptの配列応用
前のレッスンでは基本的な配列操作を学びました。ここでは配列の「パワームーブ」— 高階メソッドでレベルアップしましょう。より少ないコードでより多くのことができ、モダンJavaScript開発では不可欠なスキルです。
map — 各要素を変換する
mapはすべての要素に関数を実行し、新しい配列を返します。元の配列は変更されません。組み立てラインのように考えましょう:各要素が入り、変換され、新しい製品の一部として出てきます。
<script>
let prices = [10, 20, 30];
let doubled = prices.map(p => p * 2); // [20, 40, 60]
</script>
コールバックは3つの引数を受け取ります:(要素, インデックス, 元の配列)。ほとんどの場合、最初の1つだけが必要です。
filter — パスしたものを残す
filterはテストに合格した要素を残し、新しい配列を返します。元の配列は変更されません。セキュリティチェックポイントのように、条件を満たすアイテムは通過し、残りはフィルターされます。
<script>
let scores = [45, 80, 92, 55, 78, 98];
let passed = scores.filter(s => s >= 60); // [80, 92, 78, 98]
</script>
コールバックがtrueを返す要素は残され、falseを返す要素は破棄されます。
reduce — 1つの値に蓄積する
reduceは配列を1つの値に「圧縮」します。最も一般的な用途は合計です:
<script>
let nums = [1, 2, 3, 4, 5];
let sum = nums.reduce((acc, cur) => acc + cur, 0); // 15
</script>
パラメータの意味:
acc(アキュムレータ):実行中の合計で、前の繰り返しから引き継がれるcur(カレント):現在の要素- 2番目の引数
0はアキュムレータの初期値
💪
reduceは最初は複雑に見えますが、本質はシンプルです。ステップバイステップで配列を1つの値に圧縮します。合計、最大値の検索、出現回数のカウント —reduceはすべて対応できます。
実例:map、filter、reduceの組み合わせ
<!DOCTYPE html>
<html>
<body>
<h2>map / filter / reduce</h2>
<div id="output"></div>
<script>
let products = [
{ name: 'ノートPC', price: 999, category: '電子機器' },
{ name: 'Tシャツ', price: 19, category: '衣類' },
{ name: 'マウス', price: 39, category: '電子機器' },
{ name: 'ジーンズ', price: 49, category: '衣類' },
{ name: 'キーボード', price: 79, category: '電子機器' }
];
let html = '<strong>全商品:</strong><br>';
products.forEach(p => html += `${p.name} - $${p.price}(${p.category})<br>`);
let electronics = products.filter(p => p.category === '電子機器');
html += '<br><strong>電子機器(filter):</strong><br>';
electronics.forEach(p => html += `${p.name} - $${p.price}<br>`);
let names = products.map(p => p.name);
html += '<br><strong>商品名(map):</strong>' + names.join(', ') + '<br>';
let total = products.reduce((sum, p) => sum + p.price, 0);
html += '<br><strong>合計金額(reduce):</strong>$' + total;
document.getElementById('output').innerHTML = html;
</script>
</body>
</html>
findとfindIndex
find— 最初に一致した要素を返すfindIndex— 最初に一致したインデックスを返す
何も見つからない場合、findはundefinedを返し、findIndexは-1を返します。
<script>
let users = [
{ name: '花子', age: 20 },
{ name: '太郎', age: 17 },
{ name: '一郎', age: 25 }
];
users.find(u => u.age > 22); // { name: '一郎', age: 25 }
users.findIndex(u => u.age < 18); // 1(太郎のインデックス)
</script>
sort — 要素を並べ替える
sortは配列をインプレースで整列します(元の配列を変更します!)。デフォルトでは文字列としてソートされるため、多くの初心者が引っかかります:
<script>
let nums = [10, 1, 21, 2];
nums.sort(); // [1, 10, 2, 21] ← 文字列ソート! "10" < "2"
// 正しい:数値ソートには比較関数が必要
nums.sort((a, b) => a - b); // [1, 2, 10, 21] ← 昇順
nums.sort((a, b) => b - a); // [21, 10, 2, 1] ← 降順
</script>
sortは元の配列を変更します!元の配列を保持するには、先にコピーしましょう:[...arr].sort(...)。
実例:学生の点数ランキング
<!DOCTYPE html>
<html>
<body>
<h2>学生の点数ランキング</h2>
<div id="output"></div>
<script>
let students = [
{ name: '花子', score: 72 },
{ name: '太郎', score: 95 },
{ name: '一郎', score: 88 },
{ name: '恵子', score: 61 },
{ name: '美咲', score: 95 }
];
let html = '<strong>元の順序:</strong><br>';
students.forEach(s => html += `${s.name} - ${s.score}点<br>`);
let byScore = [...students].sort((a, b) => b.score - a.score);
html += '<br><strong>点数順(降順):</strong><br>';
byScore.forEach((s, i) => {
let medal = i === 0 ? '🥇' : i === 1 ? '🥈' : i === 2 ? '🥉' : '';
html += `${medal} ${s.name} - ${s.score}点<br>`;
});
let topStudents = students.filter(s => s.score >= 90);
html += '<br><strong>90点以上(filter):</strong><br>';
topStudents.forEach(s => html += `${s.name} - ${s.score}点<br>`);
let found = students.find(s => s.name === '一郎');
html += `<br><strong>「一郎」を検索(find):</strong>${found ? found.score + '点' : '見つかりません'}`;
document.getElementById('output').innerHTML = html;
</script>
</body>
</html>
someとevery
some— 少なくとも1つの要素がテストに合格するか?(ブール値を返す)every— すべての要素がテストに合格するか?(ブール値を返す)
<script>
let scores = [60, 75, 82, 90];
scores.some(s => s >= 90); // true — 誰かが90点以上
scores.some(s => s === 100); // false — 満点の人はいない
scores.every(s => s >= 60); // true — 全員合格
</script>
💪
someは「誰かが...か?」と問い、everyは「全員が...か?」と問いかけます。一方は存在をチェックし、もう一方は完全性をチェックします。
スプレッド演算子(...)
スプレッド演算子...は配列を「展開」します。コピーと結合によく使われます:
<script>
let a = [1, 2, 3];
let b = [...a]; // コピー: [1, 2, 3](新しい配列、参照ではない)
let c = [...a, 4, 5]; // 結合: [1, 2, 3, 4, 5]
let d = [0, ...a, 4]; // 挿入: [0, 1, 2, 3, 4]
let x = [1, 2], y = [3, 4];
let z = [...x, ...y]; // [1, 2, 3, 4]
</script>
分割代入
配列から値を抽出し、位置で変数に代入します:
<script>
let [a, b, c] = [1, 2, 3];
// a = 1, b = 2, c = 3
let [first, ...rest] = [1, 2, 3, 4, 5];
// first = 1, rest = [2, 3, 4, 5]
let [name, score] = ['花子', 95];
// name = '花子', score = 95
</script>
💪 分割代入はコードを簡潔にします。関数が複数の値を返す場合、配列にまとめて分割代入するのはよくあるパターンです。
メソッドチェーン
高階メソッドは新しい配列を返すため、次々にチェーンできます:
<script>
let result = students
.filter(s => s.score >= 60) // まず、合格した生徒をフィルター
.map(s => s.name) // 次に、名前を抽出
.sort(); // 最後に、ソート
</script>
実例:チェーンでデータを処理
<!DOCTYPE html>
<html>
<body>
<h2>メソッドチェーン</h2>
<div id="output"></div>
<script>
let employees = [
{ name: '花子', dept: 'エンジニアリング', salary: 8000 },
{ name: '太郎', dept: '営業', salary: 5000 },
{ name: '一郎', dept: 'エンジニアリング', salary: 12000 },
{ name: '恵子', dept: '営業', salary: 6500 },
{ name: '美咲', dept: 'エンジニアリング', salary: 10000 },
{ name: '健太', dept: 'マーケティング', salary: 5500 }
];
let html = '<strong>全社員:</strong><br>';
employees.forEach(e => html += `${e.name} | ${e.dept} | $${e.salary}<br>`);
let techHighSalary = employees
.filter(e => e.dept === 'エンジニアリング')
.filter(e => e.salary >= 9000)
.map(e => `${e.name}($${e.salary})`);
html += '<br><strong>エンジニアリング高収入(チェーンfilter+map):</strong><br>' + techHighSalary.join(', ');
let avgSalary = employees
.filter(e => e.dept === 'エンジニアリング')
.map(e => e.salary)
.reduce((sum, s) => sum + s, 0);
let count = employees.filter(e => e.dept === 'エンジニアリング').length;
html += `<br><br><strong>エンジニアリング平均給与:</strong>$${(avgSalary / count).toFixed(0)}`;
let allAbove3k = employees.every(e => e.salary > 3000);
let has12k = employees.some(e => e.salary >= 12000);
html += `<br><br><strong>全員の給与 > $3000?</strong>${allAbove3k ? 'はい' : 'いいえ'}`;
html += `<br><strong>誰か$12000以上?</strong>${has12k ? 'はい' : 'いいえ'}`;
document.getElementById('output').innerHTML = html;
</script>
</body>
</html>
実例:スプレッド演算子と分割代入
<!DOCTYPE html>
<html>
<body>
<h2>スプレッド演算子と分割代入</h2>
<div id="output"></div>
<script>
let a = [1, 2, 3];
let b = [...a];
b.push(4);
let html = `<strong>スプレッドでコピー:</strong><br>`;
html += `元の配列a: [${a}]<br>`;
html += `コピーb: [${b}]<br>`;
html += `bを変更してもaには影響なし<br><br>`;
let x = [1, 2], y = [3, 4];
let merged = [...x, ...y, 5];
html += `<strong>結合された配列:</strong>[${merged}]<br><br>`;
let [first, second, ...rest] = [10, 20, 30, 40, 50];
html += `<strong>分割代入:</strong><br>`;
html += `first = ${first}<br>`;
html += `second = ${second}<br>`;
html += `rest = [${rest}]<br><br>`;
let scores = [85, 92, 78, 95, 60];
let [highest, ...others] = [...scores].sort((a, b) => b - a);
html += `<strong>分割代入 + ソート:</strong><br>`;
html += `最高点 = ${highest}<br>`;
html += `その他 = [${others}]`;
document.getElementById('output').innerHTML = html;
</script>
</body>
</html>
📖 まとめ
mapは各要素を新しい配列に変換し、filterは一致する要素を残し、reduceは1つの値に蓄積するfindは最初の一致する要素を返し、findIndexは最初の一致するインデックスを返すsortはデフォルトでは文字列順 — 数値には常に比較関数を渡す:(a, b) => a - bsomeはいずれかの要素が一致するかをチェックし、everyはすべての要素が一致するかをチェックする- スプレッド演算子
...は配列をコピー・結合し、分割代入は要素を変数に抽出する - 高階メソッドは新しい配列を返し、チェーンして簡潔で読みやすいコードが書ける
❓ よくある質問
mapとforEachの違いは何ですか?mapは新しい配列を返し、forEachはundefinedを返します。結果が必要な場合はmapを使い、アクションを実行するだけの場合はforEachを使います。forEachの中で手動でpushしないでください — それはmapの仕事です。sortは元の配列を変更するのですか?sortはインプレース操作で、新しい配列を返すのではなく要素を再配置します。元の配列を保持するには、先にコピーしましょう:[...arr].sort(...)。reduceで初期値を省略できますか?📝 演習
- 配列
[3, 8, 12, 5, 9, 17, 4]が与えられたら、filterで7より大きい数値を残し、mapで2倍にして出力してください。 reduceを使って、配列['りんご', 'バナナ', 'りんご', 'さくらんぼ', 'バナナ', 'りんご']の各単語の出現回数をカウントしてください →{ りんご: 3, バナナ: 2, さくらんぼ: 1 }。- 学生の配列(
nameとscoreを持つ)が与えられたら、チェーンを使って:合格した生徒をフィルター → 点数で降順ソート → 名前を新しい配列に抽出してください。結果をページに表示してください。



