海風テラスのWebサイトが公開されてから2週間。Mikaから嬉しい連絡があった。「お客さんが増えました!」。ShimaLinkの評判は口コミで広がり、新しいクライアントからの問い合わせが3件も入っている。
しかし、問題も見えてきた。
あなた: 「ねえ、クライアントごとにアクセス数とか予約状況を確認できるダッシュボードが欲しいって言われてるんだけど…」
あなた: 「ダッシュボード?HTMLとCSSだけで作れるかな…」
Yuki: 「静的なページじゃ無理だね。データを取得して、リアルタイムに画面を更新する。それにはJavaScriptの本格的な力が必要になる。」
あなた: 「JavaScriptなら前にちょっと触ったよ。alert('Hello') みたいな。」
Yuki: 「それは序の口。今日から学ぶのは、配列やオブジェクトでデータを管理し、ループで繰り返し処理をし、非同期通信でサーバーからデータを取ってくる方法。これができると、Webアプリの世界が一気に広がるよ。」
Yuki: 「まずはShimaLinkのダッシュボードに表示するクライアントデータを扱うところから始めよう。配列とオブジェクト——これがJavaScriptのデータ構造の基礎だよ。」
あなたはエディタを開き、新しいJavaScriptファイルを作成した。静的なHTMLの世界から、動的なWebアプリの世界への第一歩が始まる。
配列とオブジェクト — データの器
ShimaLinkのダッシュボードを作るには、複数のクライアント情報を管理する必要がある。そのための基本が**配列(Array)とオブジェクト(Object)**だ。
「変数が”箱”だとしたら、配列は”棚”、オブジェクトは”引き出し付きの棚”だよ。」——Yuki
配列(Array)
配列は、複数の値を順番に並べて管理するデータ構造です。
// ShimaLinkのクライアント名リスト
const clients = ["海風テラス", "首里そば太郎", "美ら花フラワー"];
// 要素へのアクセス(0から始まる)
console.log(clients[0]); // "海風テラス"
console.log(clients.length); // 3
よく使う配列メソッド
| メソッド | 説明 | 例 |
|---|---|---|
push() | 末尾に追加 | clients.push("島豆腐屋") |
pop() | 末尾を削除 | clients.pop() |
includes() | 含むか判定 | clients.includes("海風テラス") → true |
indexOf() | 位置を検索 | clients.indexOf("海風テラス") → 0 |
slice() | 部分配列を取得 | clients.slice(0, 2) → 最初の2つ |
// 新しいクライアントを追加
clients.push("やんばるキッチン");
console.log(clients.length); // 4
オブジェクト(Object)
オブジェクトは、キーと値のペアでデータを管理します。
// クライアント情報をオブジェクトで管理
const client = {
name: "海風テラス",
owner: "Mika",
category: "カフェ",
monthlyVisits: 1250,
isActive: true
};
// プロパティへのアクセス
console.log(client.name); // "海風テラス"
console.log(client["owner"]); // "Mika"(ブラケット記法)
オブジェクトの操作
// プロパティの追加・更新
client.phone = "098-XXX-XXXX";
client.monthlyVisits = 1380;
// プロパティの削除
delete client.phone;
// キーの一覧を取得
const keys = Object.keys(client);
// ["name", "owner", "category", "monthlyVisits", "isActive"]
配列 + オブジェクト = 実践的データ
実際のアプリでは、オブジェクトの配列を扱うことが非常に多いです。
// ShimaLinkのクライアント一覧
const clients = [
{ name: "海風テラス", category: "カフェ", visits: 1250 },
{ name: "首里そば太郎", category: "飲食", visits: 890 },
{ name: "美ら花フラワー", category: "花屋", visits: 420 }
];
// 全クライアントの名前を表示
clients.forEach(client => {
console.log(client.name);
});
分割代入(Destructuring)
オブジェクトや配列から、必要な値だけを取り出せます。
// オブジェクトの分割代入
const { name, category, visits } = clients[0];
console.log(name); // "海風テラス"
// 配列の分割代入
const [first, second] = clients;
console.log(first.name); // "海風テラス"
ポイント: 配列は「順番のあるリスト」、オブジェクトは「ラベル付きデータ」。この2つを組み合わせることで、あらゆるデータを表現できます。
ループとイテレーション — 繰り返しの力
ダッシュボードには複数のクライアントを表示する必要がある。一つひとつ手書きするわけにはいかない。ここで**ループ(繰り返し処理)**の出番だ。
「プログラミングの本質は”繰り返し”にある。人間が100回やることを、コードなら1行で書ける。」——Yuki
for ループ
もっとも基本的な繰り返し構文です。
const clients = ["海風テラス", "首里そば太郎", "美ら花フラワー"];
for (let i = 0; i < clients.length; i++) {
console.log(`${i + 1}. ${clients[i]}`);
}
// 1. 海風テラス
// 2. 首里そば太郎
// 3. 美ら花フラワー
for…of ループ
配列の要素を直接取り出すシンプルな書き方です。
for (const client of clients) {
console.log(client);
}
配列の高階メソッド
モダンなJavaScriptでは、高階メソッドを使った繰り返しが主流です。
forEach — 各要素に処理を実行
clients.forEach((client, index) => {
console.log(`${index + 1}. ${client}`);
});
map — 変換して新しい配列を作る
const clientData = [
{ name: "海風テラス", visits: 1250 },
{ name: "首里そば太郎", visits: 890 },
{ name: "美ら花フラワー", visits: 420 }
];
// 名前だけの配列を作る
const names = clientData.map(c => c.name);
// ["海風テラス", "首里そば太郎", "美ら花フラワー"]
filter — 条件に合う要素だけ抽出
// 訪問数1000以上のクライアント
const popular = clientData.filter(c => c.visits >= 1000);
// [{ name: "海風テラス", visits: 1250 }]
find — 条件に合う最初の1件
const mika = clientData.find(c => c.name === "海風テラス");
// { name: "海風テラス", visits: 1250 }
reduce — 集計する
// 全クライアントの合計訪問数
const totalVisits = clientData.reduce((sum, c) => sum + c.visits, 0);
// 2560
メソッドチェーン
高階メソッドは連鎖(チェーン)させることができます。
// アクティブなクライアントの名前を、訪問数順に取得
const result = clientData
.filter(c => c.visits > 500)
.sort((a, b) => b.visits - a.visits)
.map(c => c.name);
// ["海風テラス", "首里そば太郎"]
比較表
| 方法 | 用途 | 新配列を返す? |
|---|---|---|
for | 汎用ループ | いいえ |
for...of | 配列の要素を順に処理 | いいえ |
forEach | 各要素に副作用のある処理 | いいえ |
map | 変換して新しい配列を作る | はい |
filter | 条件で絞り込む | はい |
reduce | 集計・畳み込み | 値を返す |
ポイント:
mapとfilterは元の配列を変更しない(非破壊的)。これが安全なコードを書く上で非常に重要です。
アロー関数とES6+ — モダンJavaScript
前のレッスンで => という記号が何度か登場した。これはアロー関数と呼ばれるES6(ECMAScript 2015)の新しい書き方だ。
「ES6は”JavaScriptの大改革”だったんだ。書き方がぐっとシンプルになった。」——Yuki
アロー関数
従来の function キーワードの代わりに => を使う書き方です。
// 従来の関数
function greet(name) {
return `こんにちは、${name}さん!`;
}
// アロー関数
const greet = (name) => {
return `こんにちは、${name}さん!`;
};
// 省略形(1行の場合、{} と return を省略できる)
const greet = (name) => `こんにちは、${name}さん!`;
// 引数が1つの場合、() も省略できる
const greet = name => `こんにちは、${name}さん!`;
コールバックで真価を発揮
const clients = ["海風テラス", "首里そば太郎", "美ら花フラワー"];
// 従来の書き方
clients.map(function(name) {
return name + "様";
});
// アロー関数(圧倒的に読みやすい)
clients.map(name => name + "様");
テンプレートリテラル
バッククォート(`)で囲むと、文字列の中に変数を埋め込めます。
const clientName = "海風テラス";
const visits = 1250;
// 従来の文字列結合
const msg = clientName + "の今月のアクセス数は" + visits + "です。";
// テンプレートリテラル(読みやすい!)
const msg = `${clientName}の今月のアクセス数は${visits}です。`;
// 複数行もそのまま書ける
const html = `
<div class="client-card">
<h2>${clientName}</h2>
<p>アクセス数: ${visits}</p>
</div>
`;
const / let(var はもう使わない)
// const: 再代入できない(基本はこれを使う)
const API_URL = "https://api.shimalink.com";
// let: 再代入が必要な場合に使う
let currentPage = 1;
currentPage = 2; // OK
// var: 使わない(スコープの問題がある)
スプレッド構文(…)
配列やオブジェクトを展開できます。
// 配列のコピー・結合
const oldClients = ["海風テラス", "首里そば太郎"];
const newClients = [...oldClients, "美ら花フラワー"];
// ["海風テラス", "首里そば太郎", "美ら花フラワー"]
// オブジェクトのコピー・マージ
const client = { name: "海風テラス", visits: 1250 };
const updated = { ...client, visits: 1380 };
// { name: "海風テラス", visits: 1380 }
オプショナルチェーン(?.)
深くネストしたプロパティに安全にアクセスできます。
const client = {
name: "海風テラス",
contact: {
email: "mika@example.com"
}
};
// 従来(エラーになる可能性がある)
// const phone = client.contact.phone.number; // Error!
// オプショナルチェーン(安全にundefinedを返す)
const phone = client.contact?.phone?.number; // undefined
Null合体演算子(??)
const displayName = client.nickname ?? client.name;
// nicknameがnull/undefinedならnameを使う
まとめ
| 機能 | 従来 | ES6+ |
|---|---|---|
| 関数定義 | function(x) {} | (x) => {} / x => x |
| 文字列結合 | "Hello " + name | `Hello ${name}` |
| 変数宣言 | var | const / let |
| コピー | Object.assign() | { ...obj } |
| 安全アクセス | if (a && a.b) | a?.b |
ポイント: ES6+の機能は「短く書ける」だけでなく、「意図が明確になる」ことが最大のメリット。チームで開発するShimaLinkではこれが重要です。
Promiseとfetch API — 非同期の世界
ダッシュボードに表示するデータは、サーバーから取得する必要がある。しかし、ネットワーク通信には時間がかかる。その間、画面が固まっていたらユーザーは困る。ここで登場するのが非同期処理だ。
「レストランで注文したら、料理ができるまで席で待つでしょ?その間に飲み物を飲んだりスマホを見たりできる。非同期処理も同じ。結果を待つ間に他の処理を進められるんだ。」——Yuki
同期 vs 非同期
// 同期処理(上から順に実行、完了を待つ)
console.log("1. データ取得開始");
// ← ここで3秒待つ(画面が固まる)
console.log("2. データ取得完了");
console.log("3. 画面を更新");
// 非同期処理(待っている間に他の処理を進める)
console.log("1. データ取得開始");
// ← データ取得はバックグラウンドで進行
console.log("2. 他の処理を実行");
// ← データが届いたら処理する
Promise
Promiseは「未来の結果」を表すオブジェクトです。
// Promiseの3つの状態
// pending : まだ結果が出ていない
// fulfilled : 成功した(resolve)
// rejected : 失敗した(reject)
const myPromise = new Promise((resolve, reject) => {
const success = true;
if (success) {
resolve("成功しました!");
} else {
reject("エラーが発生しました");
}
});
// .then() で成功時、.catch() で失敗時の処理
myPromise
.then(result => console.log(result))
.catch(error => console.error(error));
fetch API
fetch はサーバーにHTTPリクエストを送る組み込み関数です。Promiseを返します。
// 基本的なfetchの使い方
fetch("https://api.shimalink.com/clients")
.then(response => response.json()) // JSONとしてパース
.then(data => {
console.log(data);
// [{ name: "海風テラス", ... }, ...]
})
.catch(error => {
console.error("通信エラー:", error);
});
レスポンスの確認
fetch("https://api.shimalink.com/clients")
.then(response => {
console.log(response.status); // 200
console.log(response.ok); // true(200-299の場合)
if (!response.ok) {
throw new Error(`HTTPエラー: ${response.status}`);
}
return response.json();
})
.then(data => console.log(data))
.catch(error => console.error(error));
async / await
Promiseチェーンをさらに読みやすくする書き方です。
// Promiseチェーン版
function loadClients() {
return fetch("https://api.shimalink.com/clients")
.then(response => response.json())
.then(data => data.filter(c => c.isActive));
}
// async/await版(同じ処理をより読みやすく)
async function loadClients() {
const response = await fetch("https://api.shimalink.com/clients");
const data = await response.json();
return data.filter(c => c.isActive);
}
エラーハンドリング
async function loadClients() {
try {
const response = await fetch("https://api.shimalink.com/clients");
if (!response.ok) {
throw new Error(`HTTPエラー: ${response.status}`);
}
const data = await response.json();
return data;
} catch (error) {
console.error("クライアント一覧の取得に失敗:", error);
return []; // エラー時は空配列を返す
}
}
ダッシュボードへの応用
学んだことを組み合わせて、ShimaLinkダッシュボードのデータ取得を実装してみよう。
// ダッシュボードのデータ読み込み
async function renderDashboard() {
const clients = await loadClients();
// アクティブなクライアントを訪問数順にソート
const sorted = clients
.filter(c => c.isActive)
.sort((a, b) => b.visits - a.visits);
// HTML生成
const html = sorted
.map(c => `
<div class="client-card">
<h3>${c.name}</h3>
<p>カテゴリ: ${c.category}</p>
<p>今月のアクセス: ${c.visits.toLocaleString()}回</p>
</div>
`)
.join("");
document.getElementById("client-list").innerHTML = html;
}
// ページ読み込み時に実行
renderDashboard();
まとめ
| 概念 | 説明 | 使いどころ |
|---|---|---|
| Promise | 未来の結果を表す | 非同期処理全般 |
| fetch | HTTPリクエスト送信 | API通信 |
| .then/.catch | Promiseの結果を処理 | チェーン形式の処理 |
| async/await | Promiseを同期的に書く | 読みやすい非同期コード |
| try/catch | エラーを捕捉する | async/awaitのエラー処理 |
ポイント: 現代のWebアプリはほぼすべてが非同期処理。
async/awaitを使いこなせば、複雑なデータの流れもスッキリ書けます。
ダッシュボードの画面に、クライアントごとのアクセス数がリアルタイムで表示されている。fetch APIで取得したデータが、配列のメソッドで整形され、DOMに描画される。
// ShimaLink ダッシュボード - クライアント一覧
const clients = await fetchClients();
const activeClients = clients.filter(c => c.isActive);
console.log(`アクティブクライアント: ${activeClients.length}件`);Mika: 「すごい!うちの予約状況がリアルタイムで見えるんですね。」
あなた: 「他のクライアントのデータも一覧で見えるし、これで管理がめっちゃ楽になる!」
Yuki: 「JavaScriptでデータを自在に操れるようになったね。でも、ひとつ気になることがある。」
あなた: 「何ですか?」
Yuki: 「あなたが書いたコード、ちょっと見せてもらっていい?」
Yukiがあなたのコードを画面に映す。変数名が曖昧で、関数の引数に何が入るのか分からない箇所がいくつもあった。
あなた: 「えっ、動いてるからいいんじゃないの?」
Yuki: 「今は3人だからいいけど、チームが増えたら?クライアントが10件、50件になったら?バグの原因を探すのに何時間もかかるようになるよ。」
あなた: 「どうすれば防げるんですか?」
Yuki: 「“型”という概念を使う。TypeScriptっていうJavaScriptの拡張を導入するんだ。」
次のチャプター: Chapter 7: 型という盾 — あなたのバグが増え始め、Yukiが”型安全”という武器を授ける。TypeScriptの世界へ。
🧠 理解度チェック
Q1.JavaScriptの配列で、条件に合う要素だけを抽出するメソッドはどれ?
💡 ダッシュボードで「アクティブなクライアントだけ」を表示するときに使ったメソッドだよ。
Q2.次のコードの出力は? `const [a, b] = [10, 20, 30]; console.log(b);`
💡 クライアントデータの配列から必要な値だけ取り出す場面で使うテクニックだ。
Q3.テンプレートリテラルを囲む記号はどれ?
💡 クライアントカードのHTMLを動的に生成するとき、テンプレートリテラルを使ったのを思い出そう。
Q4.fetch APIが返すものは何?
💡 サーバーからクライアントデータを取得する際、fetchの結果をawaitで待っていたね。
Q5.async/await でエラーを捕捉する正しい方法は?
💡 ダッシュボードでネットワークエラーが発生したとき、ユーザーに何も表示されない事態を防ぐために使った方法だ。
Q6.const で宣言した変数の配列にpush()でき要素を追加できる?
💡 ShimaLinkのクライアントリストはconstで宣言したけど、新しいクライアントをpushで追加できたのを覚えてる?
Q7.オプショナルチェーン(?.)の役割は?
💡 クライアントデータに電話番号がないケースで、client.contact?.phone?.numberとアクセスしてエラーを防いだ場面を思い出そう。
❓ よくある質問
配列のmap()とforEach()の違いが分からない
**最大の違いは「戻り値があるかどうか」です。** - `map()`: 新しい配列を**返す**(変換用) - `forEach()`: 何も返さない(副作用用) ```javascript const nums = [1, 2, 3]; // map: 新しい配列を作る const doubled = nums.map(n => n * 2); // doubled = [2, 4, 6] // forEach: 各要素に対して処理を実行するだけ nums.forEach(n => console.log(n)); // 1, 2, 3 と表示される(戻り値はundefined) ``` **使い分け**: データを変換したいなら`map()`、画面に表示するなど副作用がある処理なら`forEach()`。
アロー関数で{}を使うときとreturnが必要なとき
**ルールはシンプルです:** ```javascript // {} なし → 自動的にreturnされる(暗黙のreturn) const double = n => n * 2; // {} あり → returnが必要 const double = n => { return n * 2; }; // {} ありでreturn忘れ → undefinedになる! const double = n => { n * 2; // returnがないのでundefined }; ``` **注意**: オブジェクトを返す場合は () で囲む ```javascript const makeObj = name => ({ name: name }); // ({}) がないと {} がブロックと解釈される ```
fetchで「CORS エラー」が出る
CORSエラーは、**異なるドメイン(オリジン)へのリクエストがブラウザにブロックされている**ことを意味します。 **原因**: ブラウザのセキュリティ機能で、`localhost:3000`から`api.example.com`へのリクエストが制限されている。 **解決方法**: 1. **サーバー側**: APIサーバーにCORSヘッダーを追加する 2. **開発中**: プロキシサーバーを使う 3. **一時的**: ブラウザの開発者ツールで確認 ``` Access-Control-Allow-Origin: * ``` **注意**: このエラーはブラウザ側の制限なので、ターミナルからcurlで同じAPIを叩くとエラーは出ません。
async/awaitを使ったのに値がPromise {<pending>}と表示される
**awaitをつけ忘れている**か、**async関数の外でawaitを使おうとしている**可能性があります。 ```javascript // NG: awaitがないのでPromiseオブジェクトが返る const data = fetch('/api/clients'); console.log(data); // Promise {<pending>} // OK: awaitをつける const response = await fetch('/api/clients'); const data = await response.json(); console.log(data); // 実際のデータ ``` **重要**: `await` は `async` 関数の中でしか使えません。 ```javascript // OK async function load() { const data = await fetchData(); } // NG(async関数の外) const data = await fetchData(); // トップレベルでは一部環境のみ ```
constなのに配列やオブジェクトの中身を変更できるのはなぜ?
**constが禁止するのは「再代入」であり、「中身の変更」ではありません。** ```javascript const arr = [1, 2, 3]; arr.push(4); // OK(中身の変更) arr[0] = 10; // OK(中身の変更) arr = [5, 6, 7]; // エラー!(再代入) const obj = { name: "Mika" }; obj.age = 30; // OK(プロパティ追加) obj = {}; // エラー!(再代入) ``` **イメージ**: constは「箱のラベルを固定する」だけ。箱の中身は入れ替えられる。箱自体を別の箱に取り替えることはできない。
reduce()の使い方がよく分からない
`reduce()`は配列を**1つの値に集約**するメソッドです。 ```javascript // 構文: array.reduce((累積値, 現在の要素) => 処理, 初期値) const numbers = [10, 20, 30]; // 合計を計算 const sum = numbers.reduce((acc, num) => acc + num, 0); // ステップ1: acc=0, num=10 → 10 // ステップ2: acc=10, num=20 → 30 // ステップ3: acc=30, num=30 → 60 // 結果: 60 ``` **よくある使い方**: - 合計値の計算 - 最大値・最小値の算出 - オブジェクトのグループ化 **コツ**: まずは`forEach`で書いてみて、それを`reduce`に書き換えると理解しやすいです。
スプレッド構文(...)で配列をコピーしたのに元配列も変わる
スプレッド構文は**浅いコピー(shallow copy)**です。ネストしたオブジェクトは参照がコピーされます。 ```javascript // プリミティブ値の配列 → 安全 const a = [1, 2, 3]; const b = [...a]; b.push(4); console.log(a); // [1, 2, 3](影響なし) // オブジェクトの配列 → 注意! const clients = [{ name: "Mika" }]; const copy = [...clients]; copy[0].name = "Yuki"; console.log(clients[0].name); // "Yuki"(元も変わる!) ``` **解決策**: 深いコピーが必要な場合 ```javascript const deepCopy = JSON.parse(JSON.stringify(original)); // または const deepCopy = structuredClone(original); ```
for...ofとfor...inの違いは?
**全然違うものです。混同注意!** | | for...of | for...in | |---|---|---| | 対象 | 配列の**値** | オブジェクトの**キー** | | 使いどころ | 配列のループ | オブジェクトのループ | ```javascript // for...of → 値を取得 const arr = ["a", "b", "c"]; for (const item of arr) { console.log(item); // "a", "b", "c" } // for...in → キー(インデックス)を取得 for (const index in arr) { console.log(index); // "0", "1", "2"(文字列!) } // オブジェクトにはfor...in const obj = { name: "Mika", age: 30 }; for (const key in obj) { console.log(`${key}: ${obj[key]}`); } ``` **覚え方**: of = 「値の(of)中身」、in = 「キーの(in)中身」
fetch()でPOSTリクエストを送る方法は?
fetchの第2引数にオプションオブジェクトを渡します。 ```javascript const newClient = { name: "やんばるキッチン", category: "飲食" }; const response = await fetch("https://api.shimalink.com/clients", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(newClient) }); const result = await response.json(); console.log(result); ``` **ポイント**: - `method`: HTTPメソッドを指定(デフォルトはGET) - `headers`: Content-Typeを指定 - `body`: 送信するデータ(JSON.stringifyで文字列に変換)