JavaScript:オブジェクトとMap
1学習の目的
- オブジェクトで「名前で引ける」データを扱えるようになる。配列の番号ひも付けで起きていた順序ズレの事故がなくなる。
- 分割代入・オプショナルチェーン・集計イディオムを使いこなせるようになる。APIのJSON応答や集計レポートはこの1章でできている。
2基礎解説
| やりたいこと | 書き方 | 意味 |
|---|---|---|
| オブジェクトを作る | { name: "田中", age: 30 } | キー: 値 のペア |
| 取り出す | p.name / p["age"] | 番号ではなく名前で引く |
| 安全に取り出す | p.address?.city | 途中が無くてもエラーにしない |
| 一覧を取る | Object.entries(p) | [キー, 値] の配列になる |
- 存在しないキーを引くと undefined が返り、エラーにならない。静かに undefined が混ざって計算が NaN になるのがJavaScriptの怖さ。
- 取り出しは p.name(ドット記法)が基本。キー名が変数に入っているときは p[key](ブラケット記法)を使う。
- ループは for (const [key, value] of Object.entries(p))。キーだけなら Object.keys()、値だけなら Object.values()。
- const { name, age } = person; でまとめて取り出せる(分割代入)。= "未設定" と書けば既定値も指定できる。
- 数え上げの定番は count[key] = (count[key] || 0) + 1。初回は undefined なので 0 として扱う——このイディオムは丸暗記してよい。
現場使用例:APIのJSON応答 res.data.items、設定値の管理、商品ごとの売上集計、フォームの入力値をまとめて保持。
const person = { name: "田中", age: 30 };
console.log(person.name); // 田中
console.log(person.dept); // undefined ← エラーにならない
person.dept = "営業"; // 追加
person.age = 31; // 上書き
for (const [key, value] of Object.entries(person)) {
console.log(`${key}: ${value}`);
}
// 分割代入(まとめて取り出す)
const { name, age } = person;
console.log(name, age); // 田中 31
// 安全に深い階層を取る
console.log(person.address?.city ?? "未登録"); // 未登録
// 数え上げの定番
const count = {};
for (const o of ["A", "B", "A"]) {
count[o] = (count[o] || 0) + 1;
}
console.log(count); // { A: 2, B: 1 }
3基本ドリル(10問)
出力 == 田中 / 30(2行)
ドットでつないで person.name のように書く。
const person = { name: "田中", age: 30 };
console.log(person.name);
console.log(person.age);
配列は番号、オブジェクトはキーで引く。person[0] とは書けない点に注意。
A. エラーになる B. undefined が返る C. null が返る D. 0 が返る 選択★☆☆無料
解答 == B
JavaScriptは存在しないキーに寛容。
B
エラーで止まらないのが逆に危険。undefined のまま計算に使うと NaN になり、原因が離れた場所で発覚する。
出力 == { theme: 'light', fontSize: 14 }
config.fontSize = 14; と書くだけで追加される。
const config = { theme: "light" };
config.fontSize = 14;
console.log(config);
配列の push にあたる操作。代入するだけで追加されるので、追加と更新の書き方が同じ。キー名に日本語も使えるが、英数字にするのが実務の慣習。
const scores = { 佐藤: 82, 鈴木: 95 };
for (const [name, score] of Object.____(scores)) {
console.log(`${name}:${score}点`);
}
出力 == 佐藤:82点 / 鈴木:95点(2行)
「項目」を意味する英単語。[キー, 値] のペアの配列を返す。
entries
Object.entries() は配列の entries() と同じ発想。JS05・JS06で使った分割代入 [a, b] がここでも活きる。
A. obj.has("age") B. "age" in obj C. obj.includes("age") D. obj.exists("age") 選択★☆☆無料
解答 == B
JS06で配列にも似た演算子を使った。
B
in はキーを探す(値ではない)。includes は配列用、has は Map 用で、それぞれ対象が違う。
出力 == 田中(営業)
const { name, dept } = employee; と書くと2つ同時に取り出せる。
const employee = { name: "田中", dept: "営業", age: 30 };
const { name, dept } = employee;
console.log(`${name}(${dept})`);
変数名とキー名が一致していれば自動で対応する。必要な項目だけ取り出せるので、APIの応答から欲しいデータだけ抜くときに多用する。
const user = { name: "田中" };
console.log(user.address____city ?? "未登録");
出力 == 未登録
「もし存在すれば続ける」を表す記号。クエスチョンマークとドットを組み合わせる。
?.
オプショナルチェーン。これが無いと Cannot read properties of undefined というエラーで止まる。APIの応答を扱うときの必需品。
A. エラーになる B. 値が5に上書きされる C. キーが2つになる D. 無視される 選択★★☆無料
解答 == B
同じキーを重複して持てるか?
B
キーは重複できないので、同じキーへの代入は常に上書き。これを利用して「最新の値で更新する」処理が書ける。
出力 == { A: 3, B: 2, C: 1 }
空のオブジェクト count = {} を用意し、count[o] = (count[o] || 0) + 1 で数え上げる。
const orders = ["A", "B", "A", "C", "B", "A"];
const count = {};
for (const o of orders) {
count[o] = (count[o] || 0) + 1;
}
console.log(count);
(count[o] || 0) + 1 は集計の最重要イディオム。初回は undefined なので || 0 で 0 に置き換えている。JS06で二重ループになった重複カウントが、1回のループで書けるようになった。
出力 == 244
Object.values() で値の配列が取れる。あとは for...of で足し込む。
const scores = { 佐藤: 82, 鈴木: 95, 高橋: 67 };
let total = 0;
for (const s of Object.values(scores)) {
total += s;
}
console.log(total);
Object.values() はただの配列を返すので、JS06で学んだ配列操作がすべて使える。
4実践シナリオ(5問)
3行の項目+最終行 項目数:3
Object.entries() で回す。項目数は Object.keys(employee).length。
const employee = { name: "田中", dept: "営業", age: 30 };
for (const [key, value] of Object.entries(employee)) {
console.log(`${key}: ${value}`);
}
console.log(`項目数:${Object.keys(employee).length}`);
オブジェクトには .length が無いので、Object.keys() で配列にしてから length を取る。APIの応答の中身をとりあえず全部見たいときにこの形をよく使う。
3行の成績+最終行 平均:81.3点
Object.entries() で回す。平均は Object.values() の合計を件数で割り、toFixed(1) で小数1桁にする。
const scores = { 佐藤: 82, 鈴木: 95, 高橋: 67 };
for (const [name, score] of Object.entries(scores)) {
console.log(`${name}:${score}点`);
}
const values = Object.values(scores);
let total = 0;
for (const v of values) {
total += v;
}
console.log(`平均:${(total / values.length).toFixed(1)}点`);
JS06のS02では names と scores の2本を番号でひも付けたが、オブジェクトなら1本で完結し順序ズレも起きない。同じ問題の改良版として見比べてほしい。
該当1件を出力後、最終行 要発注:1品目
Object.entries() で回して if で判定。カウンタはループの前に用意する。
const stock = { ボールペン: 120, ノート: 8, 付箋: 67 };
const threshold = 10;
let count = 0;
for (const [name, num] of Object.entries(stock)) {
if (num < threshold) {
console.log(`⚠${name}:${num}個`);
count++;
}
}
console.log(`要発注:${count}品目`);
JS06の在庫レポートをオブジェクトで書き直した形。商品名と在庫数が1つにまとまっているので、indexOf で引く必要がなくなった。
商品別3行+最終行 合計:3,800円
totals[name] = (totals[name] || 0) + amount で足し込む。配列の要素は [name, amount] の形なので分割代入で受け取れる。
const sales = [["A", 1200], ["B", 800], ["A", 500], ["C", 300], ["B", 1000]];
const totals = {};
for (const [name, amount] of sales) {
totals[name] = (totals[name] || 0) + amount;
}
let sum = 0;
for (const [name, amount] of Object.entries(totals)) {
console.log(`${name}:${amount.toLocaleString()}円`);
sum += amount;
}
console.log(`合計:${sum.toLocaleString()}円`);
D09の数え上げを加算に変えただけで売上集計になる。配列の配列(JS06)+オブジェクト集計(JS07)の合わせ技で、実務のデータ処理の大半がカバーできる。
const res = { status: 200, data: { users: [{ name: "田中", age: 30 }, { name: "鈴木", age: 25 }] } };
「ステータス:200」「ユーザー数:2人」「田中(30歳)」「鈴木(25歳)」「平均年齢:27.5歳」の5行を出力すること。 コーディング★★★無料
出力5行が完全一致
res.data.users で階層をたどる。ユーザーは for...of で回し、分割代入 { name, age } で取り出せる。
const res = { status: 200, data: { users: [{ name: "田中", age: 30 }, { name: "鈴木", age: 25 }] } };
const users = res.data.users;
console.log(`ステータス:${res.status}`);
console.log(`ユーザー数:${users.length}人`);
let totalAge = 0;
for (const { name, age } of users) {
console.log(`${name}(${age}歳)`);
totalAge += age;
}
console.log(`平均年齢:${totalAge / users.length}歳`);
APIの応答はほぼこの形(オブジェクトの中に配列、配列の中にオブジェクト)。for (const { name, age } of users) のようにループの受け取り側で直接分割代入できるのがJavaScriptの便利なところ。
5仕上げ課題
const responses = [
{ area: "東京", rating: "満足" },
{ area: "大阪", rating: "普通" },
{ area: "東京", rating: "満足" },
{ area: "福岡", rating: "不満" },
{ area: "大阪", rating: "満足" },
{ area: "東京", rating: "普通" },
{ area: "東京", rating: "満足" },
];
仕様:
① 地域別の件数を「東京:4件」の形式で出力([地域別] の見出し付き)
② 評価別の件数を同じ形式で出力([評価別] の見出し付き)
③ どちらも最初に登場した順で並べること
④ 回答数と地域数、満足率(小数1桁)を出力
出力仕様(12行):
=== アンケート集計 ===
[地域別]
東京:4件
大阪:2件
福岡:1件
[評価別]
満足:4件
普通:2件
不満:1件
---
回答数:7件 / 地域数:3
満足率:57.1%
出力12行が完全一致
地域用と評価用の空オブジェクトを2つ用意し、1回のループで両方を (obj[key] || 0) + 1 で数え上げる。文字列キーは追加した順が保たれるので並べ替えは不要。満足率は 満足の件数 / 回答数 * 100 を toFixed(1) で。
const responses = [
{ area: "東京", rating: "満足" },
{ area: "大阪", rating: "普通" },
{ area: "東京", rating: "満足" },
{ area: "福岡", rating: "不満" },
{ area: "大阪", rating: "満足" },
{ area: "東京", rating: "普通" },
{ area: "東京", rating: "満足" },
];
const areaCount = {};
const ratingCount = {};
for (const { area, rating } of responses) {
areaCount[area] = (areaCount[area] || 0) + 1;
ratingCount[rating] = (ratingCount[rating] || 0) + 1;
}
console.log("=== アンケート集計 ===");
console.log("[地域別]");
for (const [area, num] of Object.entries(areaCount)) {
console.log(`${area}:${num}件`);
}
console.log("[評価別]");
for (const [rating, num] of Object.entries(ratingCount)) {
console.log(`${rating}:${num}件`);
}
const rate = (ratingCount["満足"] / responses.length) * 100;
console.log("---");
console.log(`回答数:${responses.length}件 / 地域数:${Object.keys(areaCount).length}`);
console.log(`満足率:${rate.toFixed(1)}%`);
1回のループで2つの軸を同時に集計するのが要点。データを2周する必要はない。これはExcelのピボットテーブルに相当する処理で、ここが書ければ集計業務はほぼ自動化できる。
③の「登場順」が成立するのは、文字列キーは追加した順序が保たれるというJavaScriptの仕様のおかげ。ただし注意点があり、キーが数字だと小さい順に自動で並び替わる。{ b: 1, a: 2, 10: 3, 2: 4 } は { 2: 4, 10: 3, b: 1, a: 2 } になる。年度別集計などで順序が狂ったら、これを疑うとよい。
次章では関数を学ぶ。ここまでに書いた集計処理を部品として切り出し、何度でも呼び出せるようになる。