18ステップ
すべての段階 / 18件を表示中
STEP 0
基礎
JavaScriptとは何か ── 実行できる場所を用意する
目安 1〜2日
このステップの到達点 ── JavaScriptがどこで動く言語なのか を説明でき、ブラウザの開発者ツールで自分の書いたコードを実行できる。そして、HTMLファイルからJavaScriptを読み込んで動かせる。ここができれば、あとの17ステップは「試しながら読む」だけになります。
JavaScriptは「Webページを動かすための言語」から始まった
HTMLはページの中身 、CSSは見た目 、そしてJavaScript は動き を担当します。ボタンを押したら数が増える、入力ミスを赤く知らせる、続きを勝手に読み込む——こうした「利用者の操作に反応する部分」は、ほぼすべてJavaScriptが書いています。
今のJavaScriptは、ブラウザの中だけの言語ではありません。Node.js という実行環境を使えば、パソコンやサーバーの上でも同じ言語が動きます。つまり、画面側(フロントエンド)とサーバー側(バックエンド)を、1つの言語で書ける ようになっています。これがJavaScriptを学ぶ費用対効果がとても高い理由です。
この3つは競合しません。順番はHTML → CSS → JavaScript です。HTMLとCSSがまったく分からない状態でJavaScriptだけを学ぶと、画面の書き換え(STEP 8)でつまずきます。不安な方は、先にHTMLのタグを20個ほど、CSSの指定を20個ほど触っておくと、あとが楽になります。
用語の整理 ── ECMAScript、ES6、TypeScript
学び始めると、似た言葉がいくつも出てきて混乱します。先に整理しておきます。
よくある誤解 「JavaScriptはJavaの簡易版」ではありません。名前が似ているのは、1995年当時の宣伝上の事情によるものです。求人票で「Java」と「JavaScript」を読み違えると面接で困るので、ここははっきり区別してください。
いちばん速い実行方法 ── ブラウザの開発者ツール
まず、何もインストールせずに書き始めます。ブラウザには開発者ツール (デベロッパーツール)が最初から入っていて、その中のコンソール にコードを打つと、その場で実行できます。
ブラウザ(ChromeやEdge)で、適当なページを開く
Windows は F12、または Ctrl + Shift + I。Mac は Command + Option + I
上のタブから「Console(コンソール)」 を選ぶ
カーソルが出ている行に、下のコードを打って Enter
最初の一歩:コンソールに出力する コピー
console.log("はじめてのJavaScript");
console.log(2 + 3);
console.log("こども" + "経済新聞");
実行結果
はじめてのJavaScript
5
こども経済新聞
console.log() は、かっこの中身をコンソールに表示する 命令です。これから最後まで、いちばん多く使う命令です。「動いているか分からない」ときは、まずこれを挟む——これが実務の基本動作になります。
コンソールで慌てないために 赤い文字のエラーが出ても、パソコンは壊れません。打ち間違いを教えてくれているだけです。エラー文の読み方はSTEP 14でまとめて扱います。今は「赤が出たら、綴りを見直す」で十分です。
HTMLファイルから読み込んで動かす
コンソールは試し打ち用です。実際の開発では、HTMLファイルとJavaScriptファイルを分けて 書きます。デスクトップに js-practice というフォルダを作り、次の2つのファイルを置いてください。
index.html コピー
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>JavaScriptの練習</title>
</head>
<body>
<h1 id="title">練習ページ</h1>
<button id="btn">押してみる</button>
<!-- 読み込みは </body> の直前に置く -->
<script src="main.js"></script>
</body>
</html>
main.js コピー
console.log("main.js が読み込まれました");
const btn = document.getElementById("btn");
btn.addEventListener("click", function () {
document.getElementById("title").textContent = "押されました!";
});
確かめ方
index.html をダブルクリックしてブラウザで開き、ボタンを押すと見出しが変わります。同時にコンソールに「main.js が読み込まれました」と出ていれば成功です。中身の意味は今わからなくて構いません 。STEP 8・9で必ず分かるようになります。
なぜ script は最後に書くのか ブラウザはHTMLを上から順に読みます。script を先頭に書くと、ボタンがまだ存在しない時点でJavaScriptが動き、「そんな要素はない」というエラーになります。</body> の直前に置く か、<script src="main.js" defer> と defer を付けるのが定番です。defer は「HTMLを全部読んでから実行して」という指示です。
学ぶ順番は「値 → 加工 → 画面 → 通信」
このページの順番には理由があります。上の段階は、下の段階が書けている前提で成り立つ からです。
変数が分からなければ、条件分岐は書けない
関数が分からなければ、イベント処理は書けない
配列の扱いが分からなければ、通信で受け取ったデータを表示できない
非同期が分からなければ、APIのエラー対応ができない
この依存関係は、そのまま不具合が起きたときの調べる順番 になります。学習の順番と、実務での調査の順番はほぼ同じ です。だから下から学ぶと、知識がそのまま仕事の手順になります。
このページの使い方
一周目は読むだけ 。分からない語があっても止まらず、最後まで読み通してください。
二周目でコードを打つ 。黒い枠は、コンソールかファイルにそのまま貼って動かせます。写経(見ながら自分で打つ)を強く勧めます 。
三周目で一問一答 。各ステップ末の問いに、声に出して答えてください。答えられなければ、その節だけ読み返します。
ふりがな機能について このページは、難しい漢字や英語の略語にふりがな を付けています。画面上部の「ふりがな ON/OFF」ボタンでいつでも切り替えられ、設定は次に開いたときも引き継がれます。読み方を間違えて覚えると、会話で通じない・検索できないという実害が出ます。最初は必ずONで読んでください 。
一問一答
HTML・CSS・JavaScriptは、それぞれ何を担当しますか。
JavaScriptとJavaの関係を一言で説明してください。
<script> を </body> の直前に置くのはなぜですか。
解答を見る
HTMLは中身(構造)、CSSは見た目、JavaScriptは動き(操作への反応)。
まったく別の言語。名前が似ているだけで、文法も用途も異なる。
HTMLが上から読まれるため、先に置くと操作したい要素がまだ存在せずエラーになるから(defer を付ける方法もある)。
このステップは理解できた
STEP 1
基礎
変数とデータ型 ── 値を持つ、値を見分ける
目安 3日
このステップの到達点 ── const と let を使い分けられ、7つのデータ型を見分けられる。文字列と数値の変換で起きる事故("1" + 1 が "11" になる問題)を、自分の言葉で説明できる。
変数は「値に名前を付けた箱」
変数 とは、値に名前を付けて置いておく場所です。名前を付けておけば、あとから何度でも呼び出せます。JavaScriptの変数の宣言方法は3つありますが、実務で使うのは2つだけ です。
変数の宣言 コピー
const shopName = "こども商店"; // 変えない値 → const
let stock = 12; // あとで変える値 → let
stock = stock - 1; // OK。letは再代入できる
console.log(shopName, stock); // こども商店 11
// shopName = "べつの店"; // これはエラーになる
// TypeError: Assignment to constant variable.
迷ったら const から書く のが実務の定石です。書いている途中で「ここは入れ替えたい」と分かった時点で let に直します。こうすると「意図せず値が変わっていた」という不具合が減ります。var は、有効範囲(スコープ)の挙動が直感に反するため、新規のコードでは使いません。
名前の付け方には作法がある
✕ 避けたい書き方
const a = 1980;
const data2 = "山田";
const flg = true;
3日後の自分が読めません。何の数字か が名前から分かりません。
◯ 実務の書き方
const itemPrice = 1980;
const userName = "山田";
const isPublished = true;
単語をつなげ、2語目以降の頭を大文字にします(キャメルケース )。真偽値は is / has で始めます。
使える文字は、英数字と _ $。数字から始めることはできない
大文字と小文字は区別される(userName と username は別物)
日本語の変数名も技術的には使えますが、実務では使いません
変更しない設定値は、MAX_COUNT のように全部大文字にする慣習があります
データ型は7つ。まずは5つ覚える
データ型 とは、値の種類のことです。種類によってできる操作が違うため、見分けられることが必須です。
型を調べる:typeof コピー
console.log(typeof "こども"); // "string"
console.log(typeof 100); // "number"
console.log(typeof true); // "boolean"
console.log(typeof undefined); // "undefined"
console.log(typeof { a: 1 }); // "object"
console.log(typeof [1, 2, 3]); // "object"(配列もオブジェクトの仲間)
console.log(typeof null); // "object"(※有名な仕様上の誤り)
読み取り方
typeof null が "object" になるのは、JavaScript初期からの直っていない誤り です。仕様として残っているので、覚えるしかありません。配列かどうかを調べたいときは Array.isArray(値) を使います。
undefined と null の違い ── 実務で必ず聞かれる
覚え方 undefined=まだ入っていない /null=わざと空にした 。実務では「APIから null が返る」=サーバー側が「無い」と答えている、「undefined になる」=こちらのコードで名前を間違えている、という切り分けに直結します。
文字列の組み立て ── テンプレートリテラルを使う
文字列と変数をつなげる場面は非常に多いので、テンプレートリテラル (バッククォートで囲む書き方)を最初から使ってください。
+でつなぐ書き方と、テンプレートリテラル コピー
const name = "山田";
const count = 3;
// 昔ながらの書き方(読みにくい)
console.log("こんにちは、" + name + "さん。カートに" + count + "点あります。");
// テンプレートリテラル(推奨)
console.log(`こんにちは、${name}さん。カートに${count}点あります。`);
// 中では計算もできる
console.log(`税込 ${Math.round(1980 * 1.1)} 円`);
// 改行をそのまま書ける
const address = `〒100-0001
東京都千代田区1-1-1`;
console.log(address);
実行結果
こんにちは、山田さん。カートに3点あります。
こんにちは、山田さん。カートに3点あります。
税込 2178 円
〒100-0001
東京都千代田区1-1-1
バッククォート(`)は、Windowsでは Shift + @ 、Macでは Shift + Option + @ で入力できます。${ } の中には変数だけでなく式も書けます。
型の自動変換 ── ここが最大の事故ポイント
JavaScriptは、型が違う値どうしを計算しようとすると、勝手に型をそろえてから 実行します。これを暗黙の型変換 といいます。便利に見えて、実務では不具合の温床です。
つまずきやすい例 コピー
console.log(1 + 1); // 2
console.log("1" + 1); // "11" ← 文字列としてつながる
console.log("10" - 1); // 9 ← ひき算は数値に変換される
console.log("10" * 2); // 20
console.log(1 + true); // 2 ← true は 1 として扱われる
console.log([] + {}); // "[object Object]"
なぜ危ないのか フォームの入力欄から取った値は、数字を打っても必ず文字列 です。"10" + 5 は 105 になります。合計金額がとんでもない数字になる不具合の、いちばん多い原因がこれです。入力値は、使う前に必ず数値へ変換する と覚えてください。
明示的に型を変換する コピー
// 文字列 → 数値
console.log(Number("10") + 5); // 15 ← 基本はこれ
console.log(parseInt("10個", 10)); // 10 ← 途中まで読む(単位付き対策)
console.log(parseFloat("3.5kg")); // 3.5
// 変換に失敗すると NaN(Not a Number)になる
const wrong = Number("あいう");
console.log(wrong); // NaN
console.log(Number.isNaN(wrong)); // true ← 判定はこれで行う
// 数値 → 文字列
console.log(String(2178)); // "2178"
console.log((2178).toLocaleString()); // "2,178"(3桁区切り。表示向き)
// 小数の丸め
console.log(Math.round(3.5)); // 4(四捨五入)
console.log(Math.floor(3.9)); // 3(切り捨て)
console.log(Math.ceil(3.1)); // 4(切り上げ)
console.log((3.14159).toFixed(2)); // "3.14"(文字列になる点に注意)
小数の落とし穴 0.1 + 0.2 は 0.30000000000000004 になります。バグではなく、コンピュータが2進数で小数を扱うための誤差で、多くの言語で共通です。金額の計算では、円単位の整数で計算する (例:小数の「円」ではなく「銭」や整数の円で持つ)のが実務の鉄則です。
一問一答
const と let は、どう使い分けますか。
undefined と null の違いを説明してください。
"10" + 5 の結果と、その理由を答えてください。
入力欄の値を合計する前に必ずやることは何ですか。
解答を見る
原則 const。あとから値を入れ替える必要があるものだけ let。
undefined はまだ値が入っていない状態(JavaScriptが自動で付ける)、null は「無い」と意図して入れた値。
"105"。+ は片方が文字列だと文字列連結として扱われるため。
Number() などで数値に変換する(変換できたかを Number.isNaN() で確認するとなお良い)。
このステップは理解できた
STEP 2
文法
条件分岐 ── 場合によって処理を変える
目安 3日
このステップの到達点 ── if と switch を書き分けられ、== ではなく === を使う理由 を説明できる。「値がある/ない」を安全に判定でき、条件が深くならない書き方(早期リターン)を身につける。
比較演算子 ── まず === を体に入れる
実務の鉄則:比較は === == は型をそろえてから比べるため、0 == "" や null == undefined が true になるなど、直感に反する結果を返します。等価比較は必ず === と !== 。チームのコード検査ツール(ESLint)でも、ほぼ例外なくこれが強制されます。
if文 ── 基本の形
if / else if / else コピー
const score = 72;
if (score >= 80) {
console.log("合格(優)");
} else if (score >= 60) {
console.log("合格");
} else {
console.log("不合格");
}
// → 合格
// 条件を組み合わせる
const age = 15;
const hasTicket = true;
if (age < 18 && hasTicket) { // かつ(AND)
console.log("学生料金で入場できます");
}
if (age < 6 || age >= 65) { // または(OR)
console.log("無料です");
}
if (!hasTicket) { // 否定(NOT)
console.log("チケットを購入してください");
}
truthy と falsy ── 「値がある」の判定
JavaScriptでは、真偽値以外の値も条件として書けます。条件の場所に置いたとき false 扱いになる値をfalsy (フォルシー)、それ以外をtruthy (トゥルーシー)と呼びます。
よくある落とし穴 コピー
const count = 0;
if (count) {
console.log("在庫あり");
} else {
console.log("在庫なし"); // ← 0 は falsy なのでこちら
}
// 「未入力かどうか」を見たいのに、0 まで弾いてしまう例
const inputValue = 0;
if (!inputValue) {
console.log("入力してください"); // 0 は正しい入力なのに弾かれる
}
// 意図を明確にした書き方
if (inputValue === undefined || inputValue === null) {
console.log("入力してください");
}
// もっと短く(?? は null と undefined のときだけ右側を使う)
const safe = inputValue ?? "未入力";
console.log(safe); // 0
|| と ?? の違い a || b は「aがfalsyならb」。a ?? b は「aがnullかundefinedのときだけ b」。数量や金額など0が正しい値になり得る場面では ?? を使います。?? はヌル合体演算子 (Nullish coalescing)と読みます。
三項演算子 ── 短い分岐を1行で
条件 ? 真のとき : 偽のとき コピー
const stock = 0;
// if で書くと4行
let label;
if (stock > 0) { label = "販売中"; } else { label = "売り切れ"; }
// 三項演算子なら1行
const label2 = stock > 0 ? "販売中" : "売り切れ";
console.log(label2); // 売り切れ
// 画面表示の出し分けで多用する
const price = 1980;
console.log(`${price.toLocaleString()}円(${stock > 0 ? "在庫あり" : "入荷待ち"})`);
入れ子にしない a ? b : c ? d : e のように三項演算子を重ねると、途端に読めなくなります。2段以上になったら if か次に出る switch に書き換える ——これは実務のレビューで必ず指摘される点です。
switch文 ── 同じ値との比較が3つ以上のとき
switch の書き方 コピー
const status = "shipping";
switch (status) {
case "pending":
console.log("入金待ち");
break; // ← break を忘れると下に流れ落ちる
case "paid":
console.log("準備中");
break;
case "shipping":
console.log("発送済み");
break;
case "canceled":
case "refunded": // まとめて同じ処理にできる
console.log("取り消し");
break;
default: // どれにも当てはまらないとき
console.log("不明な状態: " + status);
}
// → 発送済み
switch の比較は === と同じ厳密比較です。break の書き忘れ は、初学者のみならず経験者もやる典型的な不具合なので、書いたら必ず目視で確認してください。
早期リターン ── 条件を深くしない書き方
✕ 入れ子が深い
if (user) { if (user.isActive) { if (user.age >= 18) { 送信する(); } } }
3段になると、条件と処理の対応が追えなくなります。
◯ ダメな条件を先に返す
if (!user) return; if (!user.isActive) return; if (user.age < 18) return; 送信する();
「除外条件を上に並べ、本題を下に書く」。実務のコードはほぼこの形です。
return は関数の中でしか使えません(STEP 4で扱います)。今は「弾く条件を先に書く」という発想 だけ覚えてください。この書き方に慣れると、あとで読みやすさが段違いになります。
動かして確かめる ── truthy / falsy 判定機
0(数値のゼロ)
"0"(文字列のゼロ)
""(空文字)
" "(スペース1つ)
null
undefined
NaN
[](空の配列)
{}(空のオブジェクト)
"false"(文字列)
判定する
値を選んで「判定する」を押してください。
[] と {} が真 になる点が、最も間違えやすいところです。「配列が空かどうか」は arr.length === 0 で判定します。
一問一答
== ではなく === を使うのはなぜですか。
falsy な値を6つ挙げてください。
|| と ?? の違いは何ですか。
空の配列 [] は条件式で真ですか、偽ですか。
解答を見る
== は型を変換してから比較するため、0 == "" のような直感に反する結果になるから。
false、0、""、null、undefined、NaN。
|| は左がfalsyなら右、?? は左がnull/undefinedのときだけ右。0や空文字を有効な値として扱いたいときは ??。
真(truthy)。中身が空でも配列という値は存在するため。空かどうかは length で見る。
このステップは理解できた
STEP 3
文法
繰り返しと配列の基本 ── 同じ処理をまとめる
目安 4日
このステップの到達点 ── 配列に値を出し入れでき、for と for...of を書き分けられる。「一覧データを1件ずつ処理する」という、実務で最も多い形を自分で書ける。
配列は「順番付きの箱の並び」
配列 (array)は、複数の値を順番に並べて1つの変数で扱うしくみです。商品一覧、記事一覧、選択肢……実務のデータはほぼすべて配列で届きます。
配列の作成と取り出し コピー
const fruits = ["りんご", "みかん", "ぶどう"];
console.log(fruits[0]); // りんご ← 番号は 0 から始まる
console.log(fruits[2]); // ぶどう
console.log(fruits[3]); // undefined(存在しない番号)
console.log(fruits.length); // 3 ← 個数
console.log(fruits[fruits.length - 1]); // ぶどう(最後の要素)
console.log(fruits.at(-1)); // ぶどう(新しい書き方)
番号は0から 1番目は [0]、2番目は [1]。この「ずれ」が原因のミスを境界の間違い と呼び、実務でも頻出します。最後の要素は length - 1 と、口に出して覚えてください。
追加・削除・検索 コピー
const list = ["A", "B", "C"];
list.push("D"); // 末尾に追加 → ["A","B","C","D"]
list.unshift("Z"); // 先頭に追加 → ["Z","A","B","C","D"]
const last = list.pop(); // 末尾を取り出して削除 → "D"
const first = list.shift(); // 先頭を取り出して削除 → "Z"
console.log(list); // ["A","B","C"]
console.log(list.includes("B")); // true 含まれるか
console.log(list.indexOf("C")); // 2 何番目か(無ければ -1)
console.log(list.join(" / ")); // "A / B / C" 文字列に連結
// 一部を取り出す(元の配列は変わらない)
console.log(list.slice(0, 2)); // ["A","B"]
// 指定位置を書き換える(元の配列が変わる)
list.splice(1, 1, "X"); // 1番目から1つ消して "X" を入れる
console.log(list); // ["A","X","C"]
「元を変える」か「新しく作る」か push・splice・sort・reverse は元の配列を書き換えます 。slice・concat・map・filter は新しい配列を返します 。この区別を意識しないと、「別の場所のデータまで変わってしまった」という原因の分かりにくい不具合になります。迷ったら、元を変えない方(新しく作る方) を選んでください。
繰り返し ── 3つの書き方
for / for...of / while コピー
const items = ["ノート", "鉛筆", "消しゴム"];
// ① for:番号が必要なとき
for (let i = 0; i < items.length; i++) {
console.log(`${i + 1}番目: ${items[i]}`);
}
// ② for...of:中身だけでよいとき(読みやすい。実務ではこちらが多い)
for (const item of items) {
console.log(item);
}
// ③ while:終わる条件が回数で決まらないとき
let stock = 3;
while (stock > 0) {
console.log(`残り${stock}個`);
stock--; // ← これを忘れると無限ループ
}
実行結果(①)
1番目: ノート
2番目: 鉛筆
3番目: 消しゴム
i++ は「iに1を足す」の短縮形です。i-- は1を引きます。for の3つの部分は、「開始値;続ける条件;1回ごとの変化」 と読みます。
無限ループに注意 while の中で条件が変わらないと、ブラウザが固まります。固まったら、そのタブを閉じれば復帰できます(Chromeなら Ctrl/Command + W)。パソコンが壊れることはありません。
break と continue
途中で抜ける・その回だけ飛ばす コピー
const nums = [3, 8, 0, 5, 12];
for (const n of nums) {
if (n === 0) continue; // 0のときはこの回を飛ばす
if (n > 10) break; // 10を超えたら繰り返し自体を終える
console.log(100 / n);
}
// 33.33... 12.5 20
入れ子の繰り返しと、二次元の配列
表のようなデータを回す コピー
const table = [
["名前", "点数"],
["山田", 82],
["佐藤", 91],
];
for (const row of table) {
console.log(row.join("\t"));
}
// 九九の3の段
for (let i = 1; i <= 9; i++) {
console.log(`3 × ${i} = ${3 * i}`);
}
繰り返しの中に繰り返しを書くと、処理の回数は掛け算で増えます (100件×100件=1万回)。件数が多いデータで三重・四重にすると、画面が固まるほど遅くなります。実務では二重までを目安 にし、それ以上必要なら設計を見直します。
一問一答
配列 arr の最後の要素を取り出す書き方を2通り答えてください。
for と for...of は、どう使い分けますか。
slice と splice の違いは何ですか。
break と continue の違いは何ですか。
解答を見る
arr[arr.length - 1] または arr.at(-1)。
番号(何番目か)が必要なら for、中身だけでよいなら読みやすい for...of。
slice は元を変えずに一部を切り出す。splice は元の配列そのものを書き換える。
break は繰り返し全体を終える。continue はその回だけ飛ばして次に進む。
このステップは理解できた
STEP 4
文法
関数 ── 処理に名前を付けて、使い回す
目安 5日
このステップの到達点 ── 関数を定義して呼び出せる。引数・戻り値・スコープ を説明でき、アロー関数を読み書きできる。ここが弱いと、この先のイベント処理も非同期処理も書けません。18ステップ中、最も重要な回 です。
関数は「材料を受け取り、結果を返す機械」
関数 とは、まとまった処理に名前を付けたものです。同じ処理を何度も書かずに済み、名前が説明になるため、コードが読みやすくなります。
関数の考え方
関数の引数と戻り値
引数として税抜価格1980を渡すと、calcTax関数が中でかけ算をして、戻り値として税込価格2178を返します。
引数(ひきすう)=入れるもの
戻り値(もどりち)=出てくるもの
税抜価格
1980
calcTax( )
中でかけ算する
税込価格
2178
渡す
return
呼び出すときに渡す値
処理の本体
呼び出した場所に戻る値
const 税込 = calcTax(1980); ← この「=」の右側が、上の一本道そのもの
関数の定義と呼び出し コピー
// 定義(この時点では実行されない)
function calcTax(price) {
return Math.round(price * 1.1);
}
// 呼び出し(ここで実行される)
console.log(calcTax(1980)); // 2178
console.log(calcTax(500)); // 550
// 戻り値は変数に入れられる
const total = calcTax(1200) + calcTax(800);
console.log(total); // 2200
引数 (ひきすう)= 関数に渡す材料。かっこの中に書く
戻り値 (もどりち)= return で返す結果。返さない関数もある
return に到達した時点で、関数はそこで終わる (以降の行は実行されない)
3つの書き方 ── 関数宣言・関数式・アロー関数
同じ処理を3通りで書く コピー
// ① 関数宣言(もっとも基本)
function double(n) {
return n * 2;
}
// ② 関数式(変数に入れる)
const double2 = function (n) {
return n * 2;
};
// ③ アロー関数(今のJavaScriptの主流)
const double3 = (n) => {
return n * 2;
};
// ③の短縮形:処理が1つの式だけなら { } と return を省ける
const double4 = (n) => n * 2;
console.log(double(5), double2(5), double3(5), double4(5)); // 10 10 10 10
巻き上げ(ホイスティング) function で宣言した関数は、ファイル内のどこからでも呼び出せます(定義が上に持ち上げられたように振る舞うため、こう呼びます)。一方 const に入れた関数は、その行より後ろでしか 呼べません。エラー Cannot access 'f' before initialization はこれが原因です。
引数のいろいろ ── デフォルト値と可変長
既定値・複数・残余引数 コピー
// 既定値(渡されなかったときに使う値)
function greet(name, suffix = "さん") {
return `${name}${suffix}、こんにちは`;
}
console.log(greet("山田")); // 山田さん、こんにちは
console.log(greet("山田", "先生")); // 山田先生、こんにちは
// 引数が渡されないと undefined になる
function sub(a, b) { return a - b; }
console.log(sub(10)); // NaN(10 - undefined)
// 個数が決まらないとき(残余引数)
function sum(...numbers) {
let total = 0;
for (const n of numbers) total += n;
return total;
}
console.log(sum(1, 2, 3)); // 6
console.log(sum(10, 20, 30, 40)); // 100
引数は3つまでを目安に 引数が4つ5つと増えると、呼び出し側で順番を間違えます。多くなったらオブジェクトを1つ渡す 形にします(STEP 5)。例:createUser({ name, email, age })。名前付きで渡せるので、順番を気にせず、読んで意味が分かります。
スコープ ── 変数が見える範囲
スコープ とは、変数が有効な範囲のことです。{ } の中で const / let を宣言すると、その { } の中だけ で使えます。
見える範囲を確かめる コピー
const shopName = "こども商店"; // ファイル全体で見える(グローバル)
function showStock() {
const stock = 10; // この関数の中だけ
console.log(shopName); // OK:外側は見える
console.log(stock);
}
showStock();
// console.log(stock); // エラー:ReferenceError: stock is not defined
if (true) {
let inner = "ブロックの中";
var old = "varは範囲が広い";
}
// console.log(inner); // エラー
console.log(old); // "varは範囲が広い" ← varの困った挙動
内側から外側は見えるが、外側から内側は見えない ——これがスコープの基本です。var は { } を無視して関数全体に広がるため、意図しない上書きが起きます。これが「var を使わない」理由です。
関数を渡す ── コールバック関数
JavaScriptでは、関数そのものを引数として渡せます 。渡された側があとで呼び出す関数をコールバック関数 といいます。イベント処理も配列メソッドも非同期処理も、すべてこの仕組みの上に成り立っています。
関数を引数に渡す コピー
function repeat(times, action) {
for (let i = 1; i <= times; i++) {
action(i); // 渡された関数を呼び出す
}
}
repeat(3, (n) => {
console.log(`${n}回目`);
});
// 1回目 / 2回目 / 3回目
// 「呼び出す」と「渡す」の違いに注意
const say = () => console.log("こんにちは");
setTimeout(say, 1000); // ◯ 1秒後に実行される(関数を渡した)
setTimeout(say(), 1000); // ✕ すぐ実行され、結果(undefined)を渡している
かっこの有無が意味を変える say は関数そのもの 、say() は関数を実行した結果 です。イベント登録で「なぜかページを開いた瞬間に動く」という不具合は、ほぼ100%これが原因です。STEP 9で再度出てきます。
良い関数の条件
1つの関数は1つの仕事だけ にする(名前が「〜と〜をする」になったら分割の合図)
名前は動詞から始める :getUser、calcTotal、isValidEmail、renderList
同じ引数なら同じ結果を返す 形(純粋関数)を基本にすると、試験も修正も簡単になる
20行を大きく超えたら、切り出せる部分がないか疑う
動かして確かめる ── 関数で税込価格を計算する
標準税率 10%
軽減税率 8%
計算する
税抜価格を入れて「計算する」を押してください。
動いているのは const calcTax = (price, rate = 0.1) => Math.round(price * (1 + rate)); というたった1行の関数 です。引数の既定値も使っています。
一問一答
引数と戻り値を、それぞれ一言で説明してください。
setTimeout(say, 1000) と setTimeout(say(), 1000) の違いは何ですか。
関数の中で const 宣言した変数を、関数の外から使えますか。
コールバック関数とは何ですか。
解答を見る
引数=関数に渡す材料。戻り値=return で関数から返す結果。
前者は関数そのものを渡すので1秒後に実行される。後者はその場で実行し、戻り値(undefined)を渡してしまう。
使えない。{ } の内側で宣言した変数は、その内側だけで有効(スコープ)。
あとで呼び出してもらうために、引数として渡す関数のこと。
このステップは理解できた
STEP 5
文法
オブジェクト ── 実務のデータはこの形で届く
目安 4日
このステップの到達点 ── オブジェクトの読み書きができ、分割代入・スプレッド・オプショナルチェーン を使える。JSONとオブジェクトの関係を説明でき、「参照のコピー」による事故を避けられる。
オブジェクトは「名札付きの箱」
配列が順番 で値を管理するのに対し、オブジェクト は名前(キー) で値を管理します。1件のデータ(ユーザー1人、商品1つ)を表すのに使います。
オブジェクトの基本 コピー
const user = {
name: "山田花子",
age: 34,
email: "hanako@example.com",
isAdmin: false,
};
// 読み取り:2通り
console.log(user.name); // 山田花子(ドット記法。基本はこちら)
console.log(user["email"]); // hanako@example.com(キーを変数で指定したいとき)
const key = "age";
console.log(user[key]); // 34
// 書き換え・追加・削除
user.age = 35;
user.tel = "03-0000-0000"; // 無いキーに代入すると追加される
delete user.isAdmin;
console.log(user);
// 無いキーを読むと undefined(エラーにはならない)
console.log(user.address); // undefined
const なのに中身を変えられる? const が禁止するのは「変数そのものの入れ替え」だけです。中身の書き換えは可能 です。user = {} はエラー、user.age = 35 はOK——この区別が実務で効いてきます。
入れ子とオブジェクトの配列 ── APIのデータの形
実務で届くデータの典型 コピー
const order = {
id: 1024,
customer: { name: "佐藤", tel: "090-0000-0000" },
items: [
{ name: "ノート", price: 180, qty: 3 },
{ name: "鉛筆", price: 90, qty: 10 },
],
};
console.log(order.customer.name); // 佐藤
console.log(order.items[1].name); // 鉛筆
console.log(order.items.length); // 2
// 1件ずつ処理する
for (const item of order.items) {
console.log(`${item.name}: ${item.price * item.qty}円`);
}
// ノート: 540円 / 鉛筆: 900円
サーバーから届くデータは、ほぼ必ず「オブジェクトの配列」 か「配列を含むオブジェクト」 です。この形をたどれるかどうかが、STEP 13(API通信)でそのまま効いてきます。
オプショナルチェーン ── 「無いかもしれない」を安全に読む
?. で落ちないようにする コピー
const data = { user: { name: "山田" } };
console.log(data.user.name); // 山田
// console.log(data.company.name); // エラー:Cannot read properties of undefined
console.log(data.company?.name); // undefined(エラーにならない)
console.log(data.company?.name ?? "未登録"); // 未登録
// 配列や関数にも使える
console.log(data.items?.[0]); // undefined
console.log(data.save?.()); // undefined(save が無くても落ちない)
頻出エラー Cannot read properties of undefined (reading 'name') は、「undefined の中の name を読もうとした」という意味です。つまり手前の階層が存在しない ということ。?. で回避できますが、まずはなぜ手前が無いのか を確かめてください(キー名の綴り違い、データがまだ届いていない、など)。
分割代入 ── 必要な値だけ取り出す
よく使う書き方 コピー
const user = { name: "山田", age: 34, city: "東京" };
// 従来の書き方
// const name = user.name;
// const age = user.age;
// 分割代入
const { name, age } = user;
console.log(name, age); // 山田 34
// 別名を付ける・既定値を入れる
const { city: pref, tel = "未登録" } = user;
console.log(pref, tel); // 東京 未登録
// 配列でも使える
const [first, second] = ["金", "銀"];
console.log(first, second); // 金 銀
// 関数の引数で使うと読みやすい(実務で多用)
function showUser({ name, age }) {
console.log(`${name}(${age}歳)`);
}
showUser(user); // 山田(34歳)
スプレッド構文 ── 複製と結合
... で広げる コピー
const base = { name: "山田", age: 34 };
// 複製しつつ一部を上書き(元は変えない。実務で最頻出)
const updated = { ...base, age: 35 };
console.log(base.age, updated.age); // 34 35
// オブジェクトの結合(後ろが優先)
const defaults = { color: "白", size: "M" };
const input = { size: "L" };
console.log({ ...defaults, ...input }); // { color: "白", size: "L" }
// 配列でも使える
const a = [1, 2];
const b = [3, 4];
console.log([...a, ...b]); // [1,2,3,4]
console.log(Math.max(...[5, 9, 3])); // 9
値のコピーと参照 ── 分かりにくい不具合の原因
数値や文字列は値そのもの が複製されますが、オブジェクトと配列は「置き場所の案内(参照)」 が複製されます。つまり、代入しただけでは中身は同じ1つ です。
同じものを指してしまう例 コピー
const a = { count: 1 };
const b = a; // 参照のコピー(同じオブジェクトを指す)
b.count = 99;
console.log(a.count); // 99 ← aまで変わってしまう
// 複製したいときはスプレッド
const c = { ...a };
c.count = 1;
console.log(a.count, c.count); // 99 1
// 入れ子は「浅いコピー」になる点に注意
const org = { name: "本社", tags: ["A", "B"] };
const copy = { ...org };
copy.tags.push("C");
console.log(org.tags); // ["A","B","C"] ← 中の配列は共有されたまま
// 入れ子ごと完全に複製する(深いコピー)
const deep = structuredClone(org);
deep.tags.push("D");
console.log(org.tags.length, deep.tags.length); // 3 4
覚え方 { ...obj } は1階層だけ の複製(浅いコピー)。入れ子まで完全に分けたいときは structuredClone(obj)(最近のブラウザで利用可)、または JSON.parse(JSON.stringify(obj))(日付や関数が消える点に注意)。
JSON ── データの共通語
JSON (ジェイソン)は、データを文字列 としてやり取りするための形式です。見た目はJavaScriptのオブジェクトとほぼ同じですが、あくまで文字列 である点が違います。サーバーとの通信、設定ファイル、ブラウザへの保存——すべてJSONが使われます。
オブジェクト ⇄ JSON文字列 コピー
const user = { name: "山田", age: 34, tags: ["管理者"] };
// オブジェクト → JSON文字列(送信・保存するとき)
const json = JSON.stringify(user);
console.log(json);
// {"name":"山田","age":34,"tags":["管理者"]}
console.log(JSON.stringify(user, null, 2)); // 読みやすく整形して出す
// JSON文字列 → オブジェクト(受信・読み込むとき)
const parsed = JSON.parse(json);
console.log(parsed.name); // 山田
console.log(typeof json, typeof parsed); // string object
JSON.parse() は、形式が壊れていると例外を投げて止まります。通信で受け取ったJSONを解析するときは、try / catch で囲むのが実務の作法です(STEP 14)。
一問一答
配列とオブジェクトの使い分けを一言で説明してください。
const user = {...} のとき、user.age = 40 はエラーになりますか。
const b = a;(aはオブジェクト)としたあと、b を変えると a はどうなりますか。
JSONとJavaScriptのオブジェクトの違いは何ですか。
解答を見る
配列は順番で管理(一覧)、オブジェクトは名前で管理(1件の中身)。
エラーにならない。const が禁止するのは変数自体の再代入だけで、中身の変更は可能。
a も変わる。参照(同じ実体への案内)をコピーしているため。複製するなら { ...a }。
JSONは文字列としてのデータ形式。キーを必ずダブルクォートで囲むなど、書き方の制約が厳しい。
このステップは理解できた
STEP 6
文法
配列メソッド ── 実務でいちばん書く処理
目安 5日
このステップの到達点 ── map / filter / find / reduce / sort を使い分けられる。「一覧を絞り込み、並べ替え、表示用の形に変える」という、業務コードの中心作業を for を書かずにこなせる。
なぜ for を卒業するのか
for でも書けますが、実務のコードは配列メソッドで書かれます。理由は「何をしているか」が一目で分かるから です。filter と書いてあれば絞り込み、map と書いてあれば変換だと、読んだ瞬間に分かります。
△ for で書くと
const result = []; for (let i = 0; i < items.length; i++) { if (items[i].price >= 1000) { result.push(items[i].name); } }
6行読んで、ようやく意図が分かります。
◯ 配列メソッドなら
const result = items .filter((item) => item.price >= 1000) .map((item) => item.name);
「1000円以上に絞って、名前だけ取り出す」とそのまま読めます 。
使うデータ
以下の例で共通して使う配列 コピー
const products = [
{ id: 1, name: "ノート", price: 180, stock: 24, category: "文具" },
{ id: 2, name: "鉛筆", price: 90, stock: 0, category: "文具" },
{ id: 3, name: "電卓", price: 1980, stock: 5, category: "事務" },
{ id: 4, name: "ホチキス", price: 1200, stock: 12, category: "事務" },
{ id: 5, name: "消しゴム", price: 120, stock: 40, category: "文具" },
];
forEach ── 1件ずつ処理する(戻り値なし)
forEach コピー
products.forEach((product, index) => {
console.log(`${index + 1}. ${product.name} — ${product.price}円`);
});
// 1. ノート — 180円
// 2. 鉛筆 — 90円 ...
コールバックは (要素, 添字, 配列全体) の順に受け取れます。使わない引数は書かなくて構いません。forEach は結果を返さない ので、新しい配列がほしいときは次の map を使います。
map ── 同じ件数のまま、形を変える
map(変換) コピー
// 名前だけの配列にする
const names = products.map((p) => p.name);
console.log(names); // ["ノート","鉛筆","電卓","ホチキス","消しゴム"]
// 税込価格を足したオブジェクトの配列にする
const withTax = products.map((p) => ({
...p,
taxIncluded: Math.round(p.price * 1.1),
}));
console.log(withTax[2].taxIncluded); // 2178
// 表示用の文字列にする(画面表示の定番)
const rows = products.map((p) => `${p.name}:${p.price.toLocaleString()}円`);
console.log(rows.join("\n"));
アロー関数でオブジェクトを返すとき (p) => { ...p } と書くと、{ が「処理のかたまり」と解釈されて失敗します。丸かっこで包んで (p) => ({ ...p }) と書きます。よく詰まる点なので、ここは丸暗記して構いません。
filter ── 条件に合うものだけ残す
filter(絞り込み) コピー
// 在庫があるものだけ
const inStock = products.filter((p) => p.stock > 0);
console.log(inStock.length); // 4
// 条件を組み合わせる
const cheapStationery = products.filter((p) => p.category === "文具" && p.price < 150);
console.log(cheapStationery.map((p) => p.name)); // ["鉛筆","消しゴム"]
// キーワード検索(大文字小文字を無視)
const keyword = "ノート";
const hit = products.filter((p) => p.name.toLowerCase().includes(keyword.toLowerCase()));
console.log(hit);
filter のコールバックは真偽値を返す のが決まりです。true を返した要素だけが新しい配列に残ります。該当なしのときは空配列 [] が返ります(null ではありません)。
find / some / every ── 探す・確かめる
1件だけ探す、条件を確かめる コピー
// 最初に見つかった1件(オブジェクトそのもの)
const target = products.find((p) => p.id === 3);
console.log(target.name); // 電卓
console.log(products.find((p) => p.id === 999)); // undefined(見つからない)
// 何番目かを知りたいとき
console.log(products.findIndex((p) => p.id === 3)); // 2
// 1つでも当てはまるか
console.log(products.some((p) => p.stock === 0)); // true(在庫切れがある)
// 全部が当てはまるか
console.log(products.every((p) => p.price > 50)); // true
find と filter を混同しない find は要素(1件) 、filter は配列 を返します。filter(...)[0] と書きたくなったら find の出番です。また find の結果は undefined になり得るので、そのまま .name を読むと落ちます(?. を使う)。
reduce ── 集計する
reduce(リデュース)は、配列を1つの値にまとめる メソッドです。合計・最大値・件数集計など、集計はこれで書きます。最初は難しく見えますが、「累積の箱を持ち回る」 と理解すれば単純です。
reduce(集計) コピー
// 合計金額(在庫数 × 価格)
const total = products.reduce((sum, p) => sum + p.price * p.stock, 0);
// ↑累積 ↑今の要素 ↑初期値
console.log(total); // 22140
// 単純な合計
console.log([1, 2, 3, 4].reduce((a, b) => a + b, 0)); // 10
// カテゴリごとの件数を数える(実務で頻出)
const countByCategory = products.reduce((acc, p) => {
acc[p.category] = (acc[p.category] ?? 0) + 1;
return acc;
}, {});
console.log(countByCategory); // { 文具: 3, 事務: 2 }
初期値を必ず書く 第2引数の初期値(0 や {})を省くと、空配列のときにエラー になります。実務では「該当0件」は普通に起きるため、初期値は必ず書いてください。
sort ── 並べ替え(元の配列を壊す点に注意)
sort(並べ替え) コピー
// ✕ そのまま使うと文字列として比較される
console.log([10, 9, 100].sort()); // [10, 100, 9]
// ◯ 数値は比較関数を渡す
console.log([10, 9, 100].sort((a, b) => a - b)); // [9, 10, 100] 昇順
console.log([10, 9, 100].sort((a, b) => b - a)); // [100, 10, 9] 降順
// オブジェクトの配列を価格の安い順に
const cheapFirst = [...products].sort((a, b) => a.price - b.price);
// ↑ コピーしてから並べ替える(元を壊さない)
console.log(cheapFirst.map((p) => p.price)); // [90,120,180,1200,1980]
// 文字列(日本語を含む)の並べ替え
const byName = [...products].sort((a, b) => a.name.localeCompare(b.name, "ja"));
console.log(byName.map((p) => p.name));
sort は元の配列を書き換えます 画面に出している元データを直接 sort すると、他の場所の表示まで並び替わります。[...arr].sort() のようにコピーしてから 並べ替えるのが実務の定石です(新しい toSorted() も同じ目的のメソッドです)。
つなげて書く ── メソッドチェーン
絞る → 並べる → 形を変える コピー
const view = products
.filter((p) => p.stock > 0) // 在庫あり
.sort((a, b) => b.price - a.price) // 高い順
.map((p) => `${p.name}(${p.price}円)`) // 表示用に整形
.slice(0, 3); // 上位3件
console.log(view);
// ["電卓(1980円)","ホチキス(1200円)","ノート(180円)"]
上から下へ、データが加工されていく流れ をそのまま書けるのが利点です。ただし1つの連結が5段を超えると読みづらくなるため、そのときは途中で変数に区切ります。
動かして確かめる ── 商品一覧を絞り込む・並べ替える
一問一答
map と filter の違いを説明してください。
find が見つからなかったときの戻り値は何ですか。
[10, 9, 100].sort() の結果と、その理由を答えてください。
reduce の第2引数(初期値)を省くと、どんな問題が起きますか。
解答を見る
map は件数を変えずに各要素を変換する。filter は条件に合う要素だけを残す(件数が減る)。
undefined。そのままプロパティを読むとエラーになるため ?. や存在確認が必要。
[10, 100, 9]。比較関数を渡さないと文字列として 比較されるため。数値は (a,b) => a - b を渡す。
空配列のときにエラーになる。また集計対象の型が意図しないものになる。
このステップは理解できた
STEP 7
文法
クラス・モジュール・this ── コードを分けて育てる
目安 4日
このステップの到達点 ── ファイルを分割して import / export でつなげられる。クラスの基本形を読み書きでき、this が何を指すか を場面ごとに説明できる。
モジュール ── 1ファイル1役割に分ける
コードが数百行になると、1つのファイルでは手に負えなくなります。JavaScriptでは、ファイルをモジュール として分け、必要なものだけを受け渡しできます。
tax.js(部品側) コピー
// 名前付きエクスポート(複数出せる)
export const TAX_RATE = 0.1;
export function calcTax(price) {
return Math.round(price * (1 + TAX_RATE));
}
// この関数はファイルの外からは見えない(公開しない)
function log(message) {
console.log(`[tax] ${message}`);
}
// 既定のエクスポート(1ファイルに1つだけ)
export default function formatPrice(price) {
return `${price.toLocaleString()}円`;
}
main.js(使う側) コピー
import formatPrice, { calcTax, TAX_RATE } from "./tax.js";
// ↑既定 ↑名前付きは { } で囲む
console.log(TAX_RATE); // 0.1
console.log(formatPrice(calcTax(1980))); // 2,178円
// 別名を付けることもできる
// import { calcTax as addTax } from "./tax.js";
HTML側(type="module" が必要) コピー
<script type="module" src="main.js"></script>
ファイルを直接開くと動きません type="module" のファイルは、セキュリティ上の制約(CORS)により file:// では動きません。簡易サーバー経由で開く 必要があります。VS Codeの拡張機能「Live Server」を入れるか、npx serve を実行してください(STEP 15)。エラー CORS policy: Cross origin requests are only supported for protocol schemes: http... が出たら、これが原因です。
クラス ── 同じ形のデータと処理をまとめる
クラスの基本形 コピー
class Cart {
#items = []; // # を付けると外から触れない(プライベート)
constructor(customerName) { // new したときに1回だけ動く
this.customerName = customerName;
}
add(name, price, qty = 1) {
this.#items.push({ name, price, qty });
return this; // 自分を返すと .add().add() とつなげられる
}
get total() { // プロパティのように読める(ゲッター)
return this.#items.reduce((sum, i) => sum + i.price * i.qty, 0);
}
summary() {
return `${this.customerName}様:${this.#items.length}点 / ${this.total}円`;
}
}
const cart = new Cart("山田");
cart.add("ノート", 180, 3).add("電卓", 1980);
console.log(cart.total); // 2520
console.log(cart.summary()); // 山田様:2点 / 2520円
現代のフロントエンド開発では、クラスを自分で書く場面は多くありません (関数とオブジェクトで足りることが多いため)。ただし、ライブラリやフレームワークの説明はクラスで書かれていることが多く、読めることは必須 です。まずは「読めればよい」で構いません。
this ── つまずきの定番
this は「呼び出され方」によって指すものが変わります 。ここを整理しておくと、あとの事故が減ります。
this が変わる場面 コピー
const timer = {
count: 0,
// ✕ 通常の関数:this が別のものになり、count が増えない
startBad() {
setInterval(function () {
this.count++; // ここの this は timer ではない
console.log(this.count); // NaN
}, 1000);
},
// ◯ アロー関数:外側の this をそのまま引き継ぐ
startGood() {
setInterval(() => {
this.count++;
console.log(this.count); // 1, 2, 3 ...
}, 1000);
},
};
実務での結論 コールバックにはアロー関数を使う 。これだけで this の問題はほぼ起きません。this が undefined になったり window を指したりしていたら、まず「通常の関数で書いていないか」を疑ってください。
クロージャ ── 状態を閉じ込める
クロージャ とは、関数が、自分が作られたときの外側の変数を覚えている 性質のことです。難しそうな名前ですが、使い道は明快です。
外から触れないカウンター コピー
function createCounter() {
let count = 0; // 外からは直接触れない
return {
increment: () => ++count,
reset: () => { count = 0; },
get current() { return count; },
};
}
const counter = createCounter();
counter.increment();
counter.increment();
console.log(counter.current); // 2
console.log(counter.count); // undefined(直接は見えない)
クロージャは、グローバル変数を増やさずに状態を持つ ための道具です。実務では、設定を閉じ込めた関数、1回だけ実行する処理、イベントの制御(連打防止など)で自然に使うことになります。
一問一答
export default と名前付き export の違いは何ですか。
モジュールを使うHTMLで必要な指定は何ですか。
コールバックにアロー関数を使うと良いのはなぜですか。
new は何をする命令ですか。
解答を見る
export default は1ファイルに1つで、読み込む側が名前を自由に付ける。名前付きは複数出せて、{ } で同じ名前で受け取る。
<script type="module" src="...">。加えて、http経由(簡易サーバー)で開く必要がある。
アロー関数は外側の this をそのまま引き継ぐため、this がずれる不具合を避けられるから。
クラス(設計図)から実体を作り、constructor を1回実行する。
このステップは理解できた
STEP 8
画面
DOM操作 ── 画面を読み取る・書き換える
目安 4日
このステップの到達点 ── HTMLの要素を取得し、文字・見た目・属性を書き換えられる。要素を新しく作って一覧を組み立てられる。textContent と innerHTML の使い分け を、安全面の理由とともに説明できる。
DOMとは「HTMLをJavaScriptから触れる形にしたもの」
ブラウザはHTMLを読み込むと、それを木のような構造に組み立てます。これをDOM (ドム/Document Object Model)といいます。JavaScriptから触るのは、HTMLの文字列ではなく、この組み立てられた木 です。
HTMLとDOMの対応
HTMLとDOMの木構造の対応
bodyの中にh1とulがあり、ulの中にliが2つあるHTMLは、documentを根として、body、h1、ul、liがぶら下がる木構造としてブラウザに読み込まれます。
① 書いたHTML
<body>
<h1 id="title">
<ul class="list">
<li>りんご</li>
<li>みかん</li>
</ul>
</body>
ブラウザが
組み立てる
② JavaScriptから触れる形(DOM)
document
body
h1#title
ul.list
li「りんご」
li「みかん」
querySelector で
この節を探し出す
JavaScriptは document を入口に、この木をたどって書き換える。
触っているのは「HTMLの文字列」ではなく「組み立てられた木」。
要素を取得する ── querySelector を覚えれば足りる
取得の基本 コピー
// CSSと同じ書き方で指定できる(最初に見つかった1つ)
const title = document.querySelector("#title"); // id
const box = document.querySelector(".card"); // class
const first = document.querySelector("ul li"); // 子孫
// 当てはまるものを全部(配列のようなもの)
const items = document.querySelectorAll(".list li");
console.log(items.length);
items.forEach((li) => console.log(li.textContent));
// 古い書き方(今も動くが、querySelectorで統一してよい)
const t2 = document.getElementById("title"); // # は書かない
// 見つからないと null が返る
const nothing = document.querySelector("#no-such-id");
console.log(nothing); // null
頻出エラー:Cannot read properties of null 「取得できていない(null)ものを操作した」という意味です。原因はほぼこの3つです。①idやclassの綴り違い ②# や . の付け忘れ ③HTMLより先にJavaScriptが動いている (script の位置、または defer の付け忘れ)。
中身を書き換える ── textContent と innerHTML
文字を入れる2つの方法 コピー
const box = document.querySelector("#box");
// ① 文字として入れる(安全。原則こちら)
box.textContent = "こんにちは";
// ② HTMLとして解釈させる(必要なときだけ)
box.innerHTML = "<b>強調</b>した文字";
// 違いが分かる例
const userInput = "<img src=x onerror=alert('攻撃')>";
box.textContent = userInput; // そのまま文字として表示される(安全)
box.innerHTML = userInput; // タグとして実行されてしまう(危険)
実務の鉄則 利用者が入力した値を innerHTML に入れてはいけません 。これは XSS (クロスサイトスクリプティング)という代表的な攻撃の入口になります。原則 textContent、どうしてもHTMLが必要なときは、自分が作った安全な文字列に限る——これを徹底してください(STEP 16で詳述)。
見た目を変える ── classList を使う
スタイルはCSSに任せ、JSはクラスを付け外しする コピー
const card = document.querySelector(".card");
card.classList.add("is-active"); // 付ける
card.classList.remove("is-hidden"); // 外す
card.classList.toggle("is-open"); // あれば外す・なければ付ける
console.log(card.classList.contains("is-active")); // true
// 直接スタイルを当てることもできる(多用しない)
card.style.display = "none";
card.style.backgroundColor = "#fff"; // CSSのbackground-color → キャメルケース
なぜ classList を優先するのか 見た目の定義がCSSに集まり、JavaScriptは「状態の切り替え」だけを担当できるからです。style を直接書くと、色や余白の指定がJSとCSSに散らばり、修正のとき両方を探すことになります。クラス名は is- で始める のが分かりやすい慣習です。
属性と入力値
属性・value・data属性 コピー
const img = document.querySelector("#photo");
img.src = "cat.jpg"; // よく使う属性は直接書ける
img.alt = "ねこの写真";
img.setAttribute("width", "320"); // 一般的な書き方
console.log(img.getAttribute("src"));
const input = document.querySelector("#name");
console.log(input.value); // 入力欄の中身は value
input.value = "初期値";
input.disabled = true; // 真偽値の属性
const check = document.querySelector("#agree");
console.log(check.checked); // チェック状態
// 自由に情報を持たせる data 属性(実務で多用)
// HTML: <button data-id="12" data-action="delete">削除</button>
const btn = document.querySelector("button");
console.log(btn.dataset.id); // "12"(文字列で来る点に注意)
console.log(btn.dataset.action); // "delete"
要素を作って追加する ── 一覧表示の基本形
配列からリストを組み立てる コピー
const fruits = ["りんご", "みかん", "ぶどう"];
const ul = document.querySelector("#fruit-list");
ul.textContent = ""; // いったん空にする(重複表示の防止)
for (const fruit of fruits) {
const li = document.createElement("li"); // 要素を作る
li.textContent = fruit; // 中身を入れる
li.classList.add("fruit-item");
ul.appendChild(li); // 親に追加する
}
// 件数が多いときは、まとめて追加すると速い
const frag = document.createDocumentFragment();
for (const fruit of fruits) {
const li = document.createElement("li");
li.textContent = fruit;
frag.appendChild(li);
}
ul.appendChild(frag); // 画面の描き直しが1回で済む
「毎回まっさらにして描き直す」 一覧を更新するとき、差分を計算しようとすると難しくなります。実務でも、まず textContent = "" で空にしてから作り直す方法が基本です(件数が数百程度なら十分に速い)。ReactやVueといったフレームワークは、この「描き直し」を自動化・効率化する道具だと理解すると腑に落ちます。
要素の削除と移動
消す・入れ替える コピー
const target = document.querySelector("#temp");
target.remove(); // 自分自身を消す
const list = document.querySelector("#list");
list.replaceChildren(); // 子要素を全部消す(新しい書き方)
// 親・子・兄弟をたどる
console.log(target.parentElement); // 親
console.log(list.children.length); // 子の数
console.log(target.closest(".card")); // 上にたどって最初に見つかる .card
closest() は、STEP 9のイベント委譲で必ず使います。「押されたボタンが、どの行のものか」を特定するときの定番です。
動かして確かめる ── DOM操作の基本セット
文字を書き換える
目立たせる(class切替)
リストに追加
ここが書き換わります
「目立たせる」は classList.toggle()、「リストに追加」は createElement() と appendChild() です。入力値は textContent で入れているので、タグを入力しても文字として表示されます(安全)。試しに <b>太字</b> と入力してみてください。
一問一答
textContent と innerHTML は、どう使い分けますか。
Cannot read properties of null というエラーの原因を3つ挙げてください。
見た目を変えるとき style より classList が推奨されるのはなぜですか。
入力欄の中身を取り出すプロパティは何ですか。
解答を見る
原則 textContent(文字として安全に入る)。HTMLとして解釈させたいときだけ innerHTML。利用者の入力は入れない。
綴り違い/#・. の付け忘れ/HTMLが読み込まれる前にJavaScriptが動いている。
見た目の定義をCSSに集約でき、JavaScriptは状態の切り替えだけを担当できるから。
value(チェックボックスは checked)。
このステップは理解できた
STEP 9
画面
イベント ── 利用者の操作に反応する
目安 4日
このステップの到達点 ── addEventListener でクリックや入力に反応でき、イベントオブジェクトから必要な情報を取り出せる。イベント委譲 を使って、あとから増えた要素にも反応できる。
基本の形
addEventListener コピー
const btn = document.querySelector("#btn");
btn.addEventListener("click", () => {
console.log("押されました");
});
// ↑種類 ↑実行したい関数(コールバック)
// 名前付き関数を渡してもよい(あとで解除できる)
function onClick() { console.log("クリック"); }
btn.addEventListener("click", onClick);
btn.removeEventListener("click", onClick); // 解除
// ✕ よくある間違い:かっこを付けると、その場で実行されてしまう
// btn.addEventListener("click", onClick());
イベントオブジェクト ── 何が起きたかを受け取る
event(e)から情報を取る コピー
const input = document.querySelector("#search");
input.addEventListener("input", (e) => {
console.log(e.target.value); // 入力された値
console.log(e.type); // "input"
});
document.addEventListener("keydown", (e) => {
if (e.key === "Escape") console.log("Escが押された");
if (e.key === "Enter" && e.ctrlKey) console.log("Ctrl+Enter");
});
// リンクの既定の動作(ページ移動)を止める
document.querySelector("#link").addEventListener("click", (e) => {
e.preventDefault(); // 移動しない
console.log("独自の処理をする");
});
e.target は実際に操作された要素 、e.currentTarget はイベントを登録した要素 です。ふだんは e.target で足りますが、次のイベント委譲でこの違いが効いてきます。
バブリングとイベント委譲
要素で起きたイベントは、親へ親へと伝わっていきます 。これをバブリング (泡が上がるようす)といいます。この性質を使うと、親に1つ登録するだけで、中の子すべての操作を受け取れます 。これがイベント委譲 です。
クリックが伝わる順(バブリング)
イベントのバブリング
buttonで起きたクリックは、まずbutton(e.target)、次に親のul#list、さらにdocumentへと外側に向かって伝わります。親のul#listで1つだけ受け取れば、中のどのボタンの操作も処理できます。
document
ul#list
← ここに1つだけ登録する
li
削除ボタン
← クリック
①
button で発生(e.target)
②
親の ul#list に伝わる
ここで受け取るのがイベント委譲
③
さらに document へ伝わる
※ 内側から外側へ「泡が上がる」ように伝わるので、バブリングと呼びます。
あとから増える要素にも効く書き方 コピー
const list = document.querySelector("#list");
// ✕ これだと、登録した時点で存在する要素にしか効かない
// document.querySelectorAll(".delete").forEach((b) => b.addEventListener(...));
// ◯ 親に1つだけ登録する(イベント委譲)
list.addEventListener("click", (e) => {
const button = e.target.closest(".delete");
if (!button) return; // 関係ない場所のクリックは無視
const id = button.dataset.id;
console.log(`${id} を削除します`);
button.closest("li").remove();
});
実務での効果 100行の表に削除ボタンがあるとき、100個の登録は不要で、親に1つで済みます。しかもあとから追加された行にも自動で効きます 。「動的に増える要素にイベントが効かない」という詰まりは、これで解決します。
連打・入力しすぎへの対策(デバウンス)
入力が止まってから処理する コピー
function debounce(fn, delay = 300) {
let timerId;
return (...args) => {
clearTimeout(timerId); // 前回の予約を取り消す
timerId = setTimeout(() => fn(...args), delay);
};
}
const search = document.querySelector("#search");
const onSearch = debounce((e) => {
console.log("検索:", e.target.value); // 入力が止まって0.3秒後に1回だけ
}, 300);
search.addEventListener("input", onSearch);
検索欄で1文字ごとにサーバーへ問い合わせると、10文字で10回の通信になります。デバウンス は、こうした無駄をなくす定番の型です。STEP 7のクロージャがそのまま使われている点にも注目してください。
動かして確かめる ── クリック・入力・委譲
−1
0
+1
リセット
一覧の「完了」「削除」は、親に1つだけ登録したイベント委譲 で処理しています。何件追加しても登録は増えません。下の黒い枠に、発生したイベントを記録しています。
一問一答
addEventListener("click", onClick) と addEventListener("click", onClick()) の違いは何ですか。
イベント委譲とは何で、どんな利点がありますか。
e.target と e.currentTarget の違いは何ですか。
検索欄で1文字ごとの通信を防ぐ手法を何といいますか。
解答を見る
前者は関数を渡す(正しい)。後者はその場で実行して戻り値を渡してしまう(ページを開いた瞬間に動く原因)。
親要素に1つだけイベントを登録し、バブリングで届いた子の操作を処理する方法。登録が1つで済み、あとから追加された要素にも効く。
e.target は実際に操作された要素、e.currentTarget はイベントを登録した要素。
デバウンス(一定時間入力が止まってから1回だけ実行する)。
このステップは理解できた
STEP 10
画面
フォーム ── 入力を受け取り、確かめて、使える形にする
目安 5日
このステップの到達点 ── フォームの送信を横取りし、各入力値を取り出して入力チェック(バリデーション) を行い、エラーを画面に表示できる。ここまでできれば、問い合わせ画面・申込画面は一人で作れます 。実務で最も依頼の多い部分です。
まずHTMLを正しく書く ── ここで9割決まる
フォームは、JavaScriptを書く前にHTMLの作りが正しいこと が前提です。特に label と name は、実装のしやすさと使いやすさの両方に直結します。
form.html(骨組み) コピー
<form id="contact-form" novalidate>
<div class="field">
<label class="field__label" for="name">お名前<span class="req">必須</span></label>
<input class="input" type="text" id="name" name="name" autocomplete="name">
<p class="field__error" data-error-for="name"></p>
</div>
<div class="field">
<label class="field__label" for="email">メールアドレス<span class="req">必須</span></label>
<input class="input" type="email" id="email" name="email" autocomplete="email">
<p class="field__error" data-error-for="email"></p>
</div>
<div class="field">
<label class="field__label" for="type">お問い合わせ種別</label>
<select class="select" id="type" name="type">
<option value="">選択してください</option>
<option value="question">ご質問</option>
<option value="request">ご要望</option>
</select>
</div>
<button class="btn" type="submit">確認する</button>
</form>
label の for と入力欄の id をそろえる :ラベルを押すと入力欄に移動でき、読み上げソフトでも項目名が伝わります
name を付ける :まとめて値を取り出すときの鍵になります
type を適切に :email や tel にすると、スマホで最適なキーボードが出ます
novalidate :ブラウザ既定のエラー表示を止め、自分で制御するための指定です
autocomplete :入力補完が効き、利用者の手間が大きく減ります
送信を横取りする ── submitイベント
ページ遷移を止めて、自分で処理する コピー
const form = document.querySelector("#contact-form");
form.addEventListener("submit", (e) => {
e.preventDefault(); // ← これが無いとページが再読み込みされる
// ① 値をまとめて取り出す
const formData = new FormData(form);
const values = Object.fromEntries(formData.entries());
console.log(values); // { name: "山田", email: "...", type: "question" }
// ② 1つずつ取り出すこともできる
const name = form.elements.name.value.trim();
console.log(name);
});
いちばん多いつまずき e.preventDefault() を書き忘れると、送信ボタンを押した瞬間にページが再読み込みされ 、コンソールのログも消えます。「押しても何も起きない(一瞬光って戻る)」ときは、まずこれを疑ってください。 また、click ではなく submit で受ける のが正解です。submit なら、入力欄でEnterキーを押したときにも同じ処理が動きます。
入力チェック ── 検証は「関数の集まり」で書く
入力チェックは、条件を if で延々と並べると手に負えなくなります。「1項目=1つの検証関数」 に分けると、増えても壊れません。
validate.js(検証部分) コピー
// 個々の検証は、エラー文(問題なければ空文字)を返す関数にする
const validators = {
name(value) {
if (!value) return "お名前を入力してください。";
if (value.length > 50) return "お名前は50文字以内で入力してください。";
return "";
},
email(value) {
if (!value) return "メールアドレスを入力してください。";
// 「@があり、その後ろに . がある」程度の緩い確認で十分
if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value)) {
return "メールアドレスの形式が正しくありません。";
}
return "";
},
tel(value) {
if (!value) return ""; // 任意項目なので空はOK
const digits = value.replace(/[^0-9]/g, ""); // 数字だけ取り出す
if (digits.length < 10 || digits.length > 11) {
return "電話番号は10桁または11桁で入力してください。";
}
return "";
},
message(value) {
if (!value) return "お問い合わせ内容を入力してください。";
if (value.length < 10) return "10文字以上で入力してください。";
if (value.length > 500) return "500文字以内で入力してください。";
return "";
},
agree(checked) {
return checked ? "" : "個人情報の取り扱いに同意してください。";
},
};
エラーの表示と送信処理 コピー
function showError(fieldName, message) {
const input = form.elements[fieldName];
const field = input.closest(".field");
const errorBox = field.querySelector(".field__error");
field.classList.toggle("is-error", message !== "");
errorBox.textContent = message;
// 読み上げソフトにも異常を伝える
input.setAttribute("aria-invalid", message !== "" ? "true" : "false");
}
form.addEventListener("submit", (e) => {
e.preventDefault();
const values = {
name: form.elements.name.value.trim(),
email: form.elements.email.value.trim(),
message: form.elements.message.value.trim(),
agree: form.elements.agree.checked,
};
// すべて検証し、エラーがあるかを集計する
let firstErrorField = null;
for (const [key, validate] of Object.entries(validators)) {
if (values[key] === undefined) continue;
const message = validate(values[key]);
showError(key, message);
if (message && !firstErrorField) firstErrorField = key;
}
if (firstErrorField) {
form.elements[firstErrorField].focus(); // 最初のエラー項目へ移動
return; // 送信しない
}
console.log("送信内容:", values); // 実際はここでサーバーへ送る(STEP 13)
});
エラー表示の作法 ①どの項目が 問題かを、その入力欄のすぐ下に書く ②どうすればよいか を書く(「不正な値です」ではなく「10文字以上で入力してください」) ③色だけに頼らない (赤色は色覚特性のある方に伝わらないことがあるため、必ず文章も出す) ④送信時に最初のエラー項目へ移動 する。この4点はそのまま実務の品質基準です。
入力中に赤くしない 入力の1文字目から「形式が違います」と出ると、まだ書いている途中の利用者に不快感を与えます。実務の定番は、①最初の検証は送信時 ②一度エラーになった項目だけ、入力するたびに再検証して解除する という組み合わせです。下のデモはこの動きで作ってあります。
実際に動くフォーム
ここまでの内容をすべて使った、動作するフォームです。わざと空のまま送信 したり、メールアドレスを abc だけにしたりして、エラーの出方を確かめてください。
動かして確かめる ── 入力チェック付きの問い合わせフォーム
このデモはどこにも送信しません 。入力した内容はこのページの中だけで処理され、押した瞬間に画面下へ表示されます。実際の送信はSTEP 13の fetch で行います。
チェックボックス・ラジオ・複数選択の取り出し方
入力の種類ごとの取り方 コピー
// 単一のチェックボックス
const agree = form.elements.agree.checked; // true / false
// 同じ name のチェックボックス(複数選択)
const checked = form.querySelectorAll('input[name="tags"]:checked');
const tags = [...checked].map((el) => el.value); // ["A","C"]
// ラジオボタン(選ばれている1つ)
const plan = form.querySelector('input[name="plan"]:checked')?.value ?? "";
// 選択欄
const type = form.elements.type.value;
// まとめて取る(複数選択にも対応)
const data = new FormData(form);
console.log(data.getAll("tags")); // ["A","C"]
入力値の整形 ── 送る前にそろえる
よく使う整形処理 コピー
const raw = " ヤマダ ハナコ ";
const trimmed = raw.trim(); // 前後の空白を除く
const noSpace = trimmed.replace(/\s+/g, " "); // 連続する空白を1つに
const halfWidth = "03-1234".replace(/[0-9]/g, (c) =>
String.fromCharCode(c.charCodeAt(0) - 0xFEE0)); // 全角数字→半角
console.log(halfWidth); // 03-1234
const digitsOnly = "03-1234-5678".replace(/[^0-9]/g, ""); // "0312345678"
const lowerEmail = "TARO@Example.COM".toLowerCase(); // "taro@example.com"
画面側のチェックは「親切」であって「安全」ではありません ブラウザ側の検証は、利用者に早く知らせるための機能です。ブラウザの検証はいくらでも回避できる ため、サーバー側でも必ず同じ検証を行う 必要があります。「フロントで見ているから大丈夫」は、実務では通用しません。
一問一答
フォームの処理を click ではなく submit で受けるのはなぜですか。
e.preventDefault() を書き忘れると何が起きますか。
入力チェックのエラー文で気をつけることを2つ挙げてください。
画面側で検証していても、サーバー側の検証が必要なのはなぜですか。
解答を見る
入力欄でEnterキーを押した送信も含めて、すべての送信操作を1か所で受け取れるから。
ページが再読み込みされ、処理結果もコンソールのログも消える。
「どの項目か」と「どう直せばよいか」を書く/色だけで示さず文章でも伝える(他に、最初のエラー項目へ移動させる、など)。
ブラウザ側の検証は利用者側で回避できるため。安全性の担保はサーバー側でしかできない。
このステップは理解できた
STEP 11
通信
非同期処理のしくみ ── 待たずに進む理由
目安 3日
このステップの到達点 ── 「JavaScriptは1本の線で動く」ことを理解し、なぜ結果が期待と違う順番で出るのか を説明できる。setTimeout の挙動と、コールバック地獄が起きる理由が分かる。
JavaScriptは、同時に2つのことをしない
JavaScriptの処理は1本の線(シングルスレッド) で進みます。つまり、重い処理を書くと、その間は画面のクリックも反応しなくなります 。
ところが、通信は数秒かかることがあります。そのあいだ画面が固まっては使い物になりません。そこでJavaScriptは、時間のかかる処理を外(ブラウザ側)に預けて、自分は次の行へ進む という方式をとります。これが非同期処理 です。
同期と非同期の違い
同期処理と非同期処理の違い
同期は、注文してから受け取るまで待ち続け、その間は何も操作できません。非同期は、時間のかかる処理をブラウザに預けて先に進み、終わったら通知を受けて結果を受け取ります。
【同期】
注文する
出来上がるまで、ただ待つ
この間、画面の操作にも反応できない
受け取る
待ち時間ぶん、
画面が固まる
【非同期】
注文する
先に進んで、ほかの作業をする
画面の操作にも反応できる
受け取る
待ち時間に、
別の処理を進められる
ブラウザ側(通信・タイマー)
時間のかかる仕事は、ここが引き受ける
預ける
終わったら通知(コールバック)
実行される順番を確かめる コピー
console.log("① 開始");
setTimeout(() => {
console.log("② 1秒後の処理");
}, 1000);
setTimeout(() => {
console.log("③ 0秒の処理");
}, 0);
console.log("④ 終了");
実行結果
① 開始
④ 終了
③ 0秒の処理
② 1秒後の処理
③は「0秒後」なのに④より後です。setTimeout に渡した処理は、いま書いている処理がすべて終わってから実行される ためです。この順番管理のしくみをイベントループ と呼びます。「0秒 = すぐ」ではなく「0秒 = 手が空いたらすぐ」と理解してください。
なぜ「結果が undefined になる」のか
非同期でいちばん多い間違いは、まだ届いていない結果を使おうとする ことです。
典型的な失敗 コピー
function getUser() {
let user;
setTimeout(() => {
user = { name: "山田" }; // 1秒後に入る
}, 1000);
return user; // ← まだ undefined のまま返してしまう
}
console.log(getUser()); // undefined
これは書き方の問題ではなく、時間の問題 です。1秒後に届く値を、いま返すことはできません。解決策は2つあり、順に発展してきました。
コールバック :「届いたら、この関数を呼んでね」と渡しておく(古い方式)
Promise / async・await :「届いたら受け取る約束」を返す(現在の主流。STEP 12)
コールバック方式 コピー
function getUser(callback) {
setTimeout(() => {
callback({ name: "山田" }); // 届いた時点で呼ぶ
}, 1000);
}
getUser((user) => {
console.log(user.name); // 1秒後に「山田」
});
コールバック地獄 ── なぜ Promise が生まれたか
順番に3つ処理したいとき(古い書き方) コピー
getUser(1, (user) => {
getOrders(user.id, (orders) => {
getDetail(orders[0].id, (detail) => {
console.log(detail);
// さらに続くと、右へ右へ深くなっていく……
}, onError);
}, onError);
}, onError);
処理が増えるほど右へ深くなり、エラー処理も各段に書く必要があります。これをコールバック地獄 と呼びます。この読みにくさを解消するために作られたのが、次のステップの Promise と async / await です。
タイマー関連の道具 setTimeout(fn, ms)=1回だけ遅らせる/setInterval(fn, ms)=繰り返す/clearTimeout(id)・clearInterval(id)=止める。setInterval は必ず止める手段を用意する こと。止め忘れると、画面を離れたあとも動き続け、動作が重くなる原因になります。
動かして確かめる ── 実行の順番
4つの処理を実行する
記録を消す
同期処理(①④)が先に出て、setTimeout(0)(③)、1秒後(②)の順に記録されます。時刻も一緒に出している ので、実際の間隔を確かめてください。
一問一答
JavaScriptが「シングルスレッド」であるとは、どういう意味ですか。
setTimeout(fn, 0) は、なぜすぐには実行されないのですか。
非同期処理の結果が undefined になるのは、なぜですか。
解答を見る
同時に1つの処理しか実行しないということ。重い処理の間は、画面の操作にも反応できない。
いま実行中の処理がすべて終わってから、順番待ちの列(イベントループ)で実行されるため。
結果が届く前に、その値を読んで(返して)しまっているため。時間差を前提にした書き方が必要になる。
このステップは理解できた
STEP 12
通信
Promiseとasync/await ── 非同期を素直に書く
目安 4日
このステップの到達点 ── async / await で非同期処理を上から下へ読める形に書け、try / catch でエラーを受け止められる。複数の処理を同時に走らせる 書き方(Promise.all)も使える。
Promise は「あとで結果を渡す約束」
Promise (プロミス)は、まだ結果が出ていない処理 を表すオブジェクトです。状態は3つしかありません。
Promiseを作る・使う コピー
// 指定ミリ秒待つ関数(実務でもよく自作する)
function sleep(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
// 成功・失敗する処理の例
function fetchUser(id) {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (id > 0) resolve({ id, name: "山田" }); // 成功 → resolve
else reject(new Error("IDが不正です")); // 失敗 → reject
}, 500);
});
}
// .then / .catch で受け取る
fetchUser(1)
.then((user) => console.log(user.name))
.catch((error) => console.error(error.message))
.finally(() => console.log("完了(成功でも失敗でも通る)"));
async / await ── 上から下へ読める形にする
.then() のつなぎ書きも動きますが、実務では async / await で書きます 。同期処理と同じ見た目になり、圧倒的に読みやすいためです。
△ .then のつなぎ書き
fetchUser(1) .then((user) => fetchOrders(user.id)) .then((orders) => console.log(orders)) .catch((e) => console.error(e));
途中の値を使い回したいとき、書き方が急に難しくなります。
◯ async / await
try { const user = await fetchUser(1); const orders = await fetchOrders(user.id); console.log(user, orders); } catch (e) { console.error(e); }
上から順に読めて、途中の値も普通の変数として使えます。
async / await の書き方 コピー
// ① 関数の前に async を付ける
async function showUser(id) {
try {
// ② Promiseを返す処理の前に await を付ける(結果が出るまで待つ)
const user = await fetchUser(id);
console.log(user.name);
return user; // asyncの戻り値は自動的にPromiseになる
} catch (error) {
// ③ 失敗はここに来る
console.error("取得に失敗:", error.message);
return null;
} finally {
console.log("処理終了");
}
}
// アロー関数なら
const showUser2 = async (id) => { /* ... */ };
// 呼び出し側でも結果を待てる
const user = await showUser(1);
同時に走らせる ── Promise.all
await を並べると、1つずつ順番に待ってしまいます 。互いに関係のない処理は、同時に走らせるほうが圧倒的に速くなります。
直列と並列 コピー
// ✕ 直列:1秒 + 1秒 + 1秒 = 約3秒
const a = await sleep(1000);
const b = await sleep(1000);
const c = await sleep(1000);
// ◯ 並列:同時に走るので約1秒
const [x, y, z] = await Promise.all([sleep(1000), sleep(1000), sleep(1000)]);
// 1つでも失敗したら全体が失敗する(在庫と価格の両方が必要、など)
try {
const [user, orders] = await Promise.all([fetchUser(1), fetchOrders(1)]);
} catch (e) {
console.error("どれかが失敗しました", e);
}
// 失敗しても他の結果はほしいとき
const results = await Promise.allSettled([fetchUser(1), fetchUser(-1)]);
console.log(results);
// [{status:"fulfilled", value:{...}}, {status:"rejected", reason:Error}]
使い分け 前の結果を次で使うなら直列 (await を並べる)。互いに独立しているなら並列 (Promise.all)。画面の初期表示で3つのAPIを叩くような場面では、並列にするだけで体感速度が3倍変わります。
よくある間違い
見つけにくい3つの罠 コピー
// ① await の付け忘れ → Promiseオブジェクトがそのまま入る
const user = fetchUser(1);
console.log(user.name); // undefined
console.log(user); // Promise { <pending> } ← この表示が出たら付け忘れ
// ② forEach の中の await は待ってくれない
[1, 2, 3].forEach(async (id) => {
await fetchUser(id); // 外側は待たずに次へ進む
});
console.log("終わった"); // ← 実際はまだ終わっていない
// ◯ 順番に待つなら for...of
for (const id of [1, 2, 3]) {
await fetchUser(id);
}
// ◯ 同時でよいなら map + Promise.all
await Promise.all([1, 2, 3].map((id) => fetchUser(id)));
// ③ catch を書かない → 握りつぶされる(Unhandled promise rejection)
fetchUser(-1); // 失敗しても誰も気づかない
コンソールに Promise { <pending> } と出たら それは await の付け忘れです。非同期の不具合の大半がこれで、しかもエラーが出ずに undefined が伝わっていく ため、原因が遠くで現れます。「値が undefined」で困ったら、まず await の有無を確認してください。
動かして確かめる ── 直列と並列の速さの違い
直列で3件処理(await を並べる)
並列で3件処理(Promise.all)
記録を消す
1件あたり0.8秒かかる処理を3件実行します。直列は約2.4秒、並列は約0.8秒。同じ処理内容でも、書き方だけで3倍違う ことを体感してください。
一問一答
Promiseの3つの状態を挙げてください。
await はどこで使えますか。
コンソールに Promise { <pending> } と表示されたとき、何を疑いますか。
独立した3つの通信を速く終わらせるには、どう書きますか。
解答を見る
pending(処理中)、fulfilled(成功)、rejected(失敗)。
async を付けた関数の中(モジュールの最上位でも可)。
await の付け忘れ。Promiseそのものを値として扱ってしまっている。
await Promise.all([...]) で並列に実行する。
このステップは理解できた
STEP 13
通信
fetchとAPI通信 ── サーバーとデータをやり取りする
目安 5日
このステップの到達点 ── fetch でデータを取得・送信でき、読み込み中・成功・失敗・0件 の4状態を画面に出し分けられる。ステータスコードとCORSの意味を説明できる。ここまでで「画面を作る仕事」の基本形は完成 です。
APIとは「データをくれる窓口」
API (エーピーアイ)とは、プログラムから呼び出せる窓口のことです。Web開発では、決められたURLにお願いを送ると、JSONでデータが返ってくる しくみを指すのがふつうです。
通信の流れ
fetchによるAPI通信の流れ
ブラウザのJavaScriptがGETリクエストを送り、サーバーがデータを調べてJSONを200 OKで返し、ブラウザがそれを解釈して画面に表示します。待っている間は読み込み中の表示を出します。
ブラウザ(あなたのJS)
サーバー
① GET /api/products
② データを調べる
③ JSON を返す(200 OK)
④ 画面に表示する
待っている間は「読み込んでいます…」
失敗・0件のときの表示も必ず用意する
※ 本文をJavaScriptのオブジェクトに変換するのが res.json()。これも待つ処理(await)。
fetch の基本 ── GETで取得する
最小の形 コピー
async function getProducts() {
const response = await fetch("https://example.com/api/products");
// ★重要:404や500でも例外にならない。自分で確認する必要がある
if (!response.ok) {
throw new Error(`通信に失敗しました(${response.status})`);
}
const data = await response.json(); // 本文をJSONとして解釈(これも非同期)
return data;
}
const products = await getProducts();
console.log(products);
最重要の注意点 fetch は、サーバーが404や500を返しても「成功」として扱います (通信自体はできたため)。例外になるのは、ネットワーク断など通信そのものが成立しなかったときだけです。if (!response.ok) throw ... を必ず書く ——これは実務レビューで最も指摘される点です。
POSTで送信する ── フォームの送信
JSONを送る コピー
async function sendContact(values) {
const response = await fetch("https://example.com/api/contact", {
method: "POST",
headers: {
"Content-Type": "application/json", // 本文の形式を伝える
},
body: JSON.stringify(values), // オブジェクト → JSON文字列
});
if (!response.ok) {
// サーバーがエラーの詳細をJSONで返すこともある
const detail = await response.json().catch(() => null);
throw new Error(detail?.message ?? `送信に失敗しました(${response.status})`);
}
return await response.json();
}
// STEP 10のフォームと組み合わせる
form.addEventListener("submit", async (e) => {
e.preventDefault();
const button = form.querySelector("button[type=submit]");
button.disabled = true; // ★二重送信を防ぐ
try {
await sendContact({ name: "山田", message: "こんにちは" });
alert("送信しました");
} catch (error) {
alert(error.message);
} finally {
button.disabled = false; // 成功でも失敗でも必ず戻す
}
});
二重送信の防止は必須 通信中にボタンを連打されると、同じ問い合わせが何件も登録されます。送信開始でボタンを無効化し、finally で戻す ——これは必ず入れてください。実際の事故として最も多いものの1つです。
4つの状態を画面に出し分ける
実務の通信処理で本当に大事なのは、fetch の書き方よりも状態の管理 です。次の4つを必ず設計してください。
状態を出し分ける実装の型 コピー
const view = document.querySelector("#view");
async function render() {
view.textContent = "読み込んでいます…"; // ① 読み込み中
try {
const products = await getProducts();
if (products.length === 0) { // ③ 0件
view.textContent = "該当する商品がありませんでした。";
return;
}
view.replaceChildren(); // ② 成功
for (const p of products) {
const li = document.createElement("li");
li.textContent = `${p.name}(${p.price}円)`;
view.appendChild(li);
}
} catch (error) { // ④ 失敗
console.error(error);
view.textContent = "読み込みに失敗しました。時間をおいて再度お試しください。";
}
}
CORS ── 別のサイトのAPIを叩くと出る壁
ブラウザには、別のドメインへの通信を、相手の許可がなければ読ませない という安全上の決まりがあります。これをCORS (コルス/オリジン間リソース共有)といいます。
よく見るエラー
Access to fetch at 'https://api.example.com/data' from origin
'http://localhost:3000' has been blocked by CORS policy:
No 'Access-Control-Allow-Origin' header is present on the requested resource.
これはJavaScriptのコードの誤りではありません 。「相手のサーバーが、このサイトからの読み取りを許可していない」という意味です。解決はサーバー側でしかできません (サーバーで許可設定を入れる、自社サーバー経由で取得する、など)。ブラウザ側の書き方をいくら変えても直りません。ここを知らずに何時間も溶かす人が非常に多いので、覚えておいてください。
中断とタイムアウト
AbortController コピー
// 5秒で打ち切る
const controller = new AbortController();
const timer = setTimeout(() => controller.abort(), 5000);
try {
const res = await fetch(url, { signal: controller.signal });
const data = await res.json();
} catch (error) {
if (error.name === "AbortError") {
console.log("時間内に応答がありませんでした");
} else {
throw error;
}
} finally {
clearTimeout(timer);
}
// 検索の入力ごとに、前の通信を取り消す用途でも使う
動かして確かめる ── 読み込み中・成功・0件・失敗の出し分け
成功(3件返る)
成功したが0件
失敗(404 見つからない)
失敗(500 サーバーエラー)
失敗(通信そのものが不通)
データを取得する
「データを取得する」を押してください。
このデモは実際の通信は行わず 、疑似的にサーバーの応答を作って動かしています。どの場合でも画面が真っ白にならない ことを確かめてください。
一問一答
fetch は404のとき例外を投げますか。どう対処しますか。
通信処理で用意すべき4つの状態を挙げてください。
CORSエラーは、どこを直せば解決しますか。
二重送信を防ぐには、どう書きますか。
解答を見る
投げない。if (!response.ok) throw new Error(...) のように自分で判定する。
読み込み中/成功(データあり)/成功だが0件/失敗。
サーバー側(許可の設定、または自社サーバーを経由させる)。ブラウザ側のコードでは解決できない。
送信開始でボタンを disabled = true にし、finally で必ず戻す。
このステップは理解できた
STEP 14
開発環境
エラー処理とデバッグ ── 自力で原因にたどり着く
目安 4日
このステップの到達点 ── エラーメッセージを読んで原因の場所を特定でき、try / catch を適切な位置に書ける。開発者ツールのブレークポイント で処理を止め、変数の中身を確認できる。「調べ方を知っている」ことが、実務では文法知識より価値があります 。
エラーメッセージは3つの部分を読む
エラー表示の例
Uncaught TypeError: Cannot read properties of null (reading 'value')
at handleSubmit (main.js:42:31)
at HTMLFormElement.<anonymous> (main.js:12:9)
① 種類 :TypeError ② 内容 :nullの中の value を読もうとした ③ 場所 :main.js の42行31文字目、handleSubmit の中。まず③の場所を開く ——これが鉄則です。エラーは怖いものではなく、場所を教えてくれる案内 です。
try / catch ── 止まってほしくない場所を守る
基本形と、投げる側 コピー
try {
const data = JSON.parse(text); // 失敗する可能性がある処理
console.log(data.name);
} catch (error) {
console.error("解析に失敗:", error.message);
// 利用者には分かる言葉で伝える
} finally {
console.log("成功でも失敗でも必ず通る");
}
// 自分でエラーを投げる
function withdraw(balance, amount) {
if (amount <= 0) throw new Error("金額は1円以上で指定してください");
if (amount > balance) throw new Error("残高が不足しています");
return balance - amount;
}
try {
withdraw(1000, 5000);
} catch (error) {
console.error(error.message); // 残高が不足しています
}
握りつぶさない catch (e) { } と中身を空にする のは最悪の書き方です。問題が起きても誰も気づけず、原因調査が不可能になります。最低でも console.error(e) を書き、利用者には状況が分かる文言を画面に出す ようにしてください。
どこに書くか try / catch を至るところに書くと、かえって読みにくくなります。原則は「失敗し得る境界」 に置くこと——①通信(fetch) ②解析(JSON.parse) ③外部ライブラリの呼び出し ④利用者の操作の入口(イベント処理)。この4か所を押さえれば十分です。
console を使い分ける
console の便利な使い方 コピー
console.log("ふつうの出力");
console.warn("注意"); // 黄色で出る
console.error("異常"); // 赤で出る。呼び出し経路も表示
// 変数名と値をまとめて出す({ } で囲むのがこつ)
const name = "山田", age = 34;
console.log({ name, age }); // { name: "山田", age: 34 }
// 配列やオブジェクトの一覧を表で見る(非常に便利)
console.table([{ id: 1, name: "A" }, { id: 2, name: "B" }]);
// 条件を満たさないときだけ出す
console.assert(age >= 18, "18歳未満です");
// 処理時間を測る
console.time("処理");
// ...重い処理...
console.timeEnd("処理"); // 処理: 123.4ms
// まとめて折りたたむ
console.group("送信内容");
console.log(name);
console.groupEnd();
ブレークポイント ── console.log より速く原因に着く
開発者ツールのSources(ソース) タブでは、コードの行番号をクリックするとブレークポイント を置けます。そこで処理が止まり、その瞬間の変数の中身がすべて見られます 。
開発者ツール → Sources タブ → 左のファイル一覧から対象のJSを開く
止めたい行の行番号をクリック (青い印が付く)
画面を操作すると、その行で止まる
変数にマウスを乗せると値が見える。右の Scope にも一覧が出る
F10 =次の行へ/F11 =関数の中へ入る/F8 =次まで進める
コードから止める コピー
function handleSubmit(values) {
debugger; // ← 開発者ツールが開いていると、この行で止まる
console.log(values);
}
// ※ 消し忘れて公開しないよう注意(ESLintで検出できる:STEP 15)
調べ方の型 ①エラーの行を開く ②その手前に console.log を置き、値が期待どおりか を見る ③期待と違えば、さらに上流 へさかのぼる ④「どこまでは正しく、どこから違うか」の境目 を見つける。この二分探索のやり方は、どんな言語でも通用します。当てずっぽうで直さないこと が最大のこつです。
ネットワークタブ ── 通信の不具合はここを見る
Network タブを開いた状態で操作すると、通信が一覧に出る
Status 列でコードを確認(200/404/500)
行をクリック → Payload で送った内容、Response で返ってきた内容を確認
「そもそもリクエストが出ていない」 ことも多い(イベントが動いていない、URLの組み立てミス)
「サーバーが悪いのか、こちらが悪いのか」は、Networkタブを見れば1分で切り分けられます 。送った内容が意図どおりなら相手側、意図と違うならこちら側です。この切り分けができると、他部署とのやり取りが一気に速くなります。
動かして確かめる ── エラーを起こして読んでみる
存在しない要素を操作する
undefined の中を読む
関数でないものを呼ぶ
壊れたJSONを解析する
自分でエラーを投げる
実行する
記録を消す
try / catch で受け止め、エラーの種類(name)と内容(message) を表示しています。ブラウザの開発者ツールを開いた状態で試すと、同じ内容がコンソールにも出ることを確認できます。
一問一答
エラーメッセージから最初に読み取るべき情報は何ですか。
catch の中を空にしてはいけないのはなぜですか。
Unexpected token < in JSON は、何が起きていますか。
通信の不具合で、まず開くべき開発者ツールのタブはどれですか。
解答を見る
発生した場所(ファイル名と行番号)。次に種類と内容。
問題が起きたことに誰も気づけず、原因調査ができなくなるから。
JSONを期待した場所にHTML(エラーページなど)が返ってきている。URLや認証の誤りを疑う。
Network(ネットワーク)タブ。ステータスコードと送受信の内容を確認する。
このステップは理解できた
STEP 15
開発環境
開発環境 ── Node.js、npm、ビルド、TypeScript
目安 5日
このステップの到達点 ── Node.jsとnpmの役割を説明でき、package.json を読める。チームの環境に参加して、開発サーバーを立ち上げられる。TypeScriptが何を解決する道具かを理解する。
Node.js ── ブラウザの外でJavaScriptを動かす
Node.js は、JavaScriptをパソコンやサーバー上で動かす実行環境です。フロントエンドの開発でも、ツールを動かす土台 として必ず必要になります。
インストールの確認(ターミナル/コマンドプロンプト) コピー
# 入っているか確認(数字が出ればOK)
node -v
npm -v
# 入っていなければ nodejs.org から LTS版 を入れる
# ファイルを実行する
node main.js
LTS版 (Long Term Support=長期サポート版)を選んでください。最新版は不具合に当たることがあります。複数の版を切り替える必要が出たら、nvm や Volta といったバージョン管理ツールを使います(最初は不要です)。
npm ── 部品を取り寄せる仕組み
npm (エヌピーエム)は、世界中の開発者が公開しているJavaScriptの部品(パッケージ )を取り寄せ、管理する道具です。
よく使うコマンド コピー
# 新しいプロジェクトを始める(package.json ができる)
npm init -y
# パッケージを入れる
npm install dayjs # 本番でも使うもの
npm install --save-dev eslint # 開発時だけ使うもの(-D と省略可)
# 既存プロジェクトに参加したとき(これが最も使う)
npm install # package.json を見て、必要なものを全部入れる
# 実行する
npm run dev # package.json の scripts に書かれた命令
npm run build
# 入れずに1回だけ実行する
npx serve # 簡易サーバーを立てる(STEP 7のCORS対策)
package.json の読み方 コピー
{
"name": "my-app",
"version": "1.0.0",
"type": "module", // import/export を使う指定
"scripts": { // npm run ○○ で実行できる命令の一覧
"dev": "vite",
"build": "vite build",
"lint": "eslint src"
},
"dependencies": { // 本番でも必要
"dayjs": "^1.11.10"
},
"devDependencies": { // 開発時だけ必要
"eslint": "^9.0.0",
"vite": "^5.0.0"
}
}
版の記号の意味 ^1.11.10 は「1系のうち、1.11.10以上の最新」を意味します。npm install のたびに細かい版が変わり得るため、package-lock.json を必ずGitに含めます。「自分の環境では動くのに、他の人の環境で動かない」の主因がここです。
ビルドツール ── なぜ必要なのか
ブラウザ用のコードを、そのまま公開することは今では少なく、ビルド という変換工程を挟みます。理由は次のとおりです。
まとめる(バンドル) :数十個のファイルへの通信を1〜数個に減らす
小さくする(最小化) :空白や改行、長い変数名を削って軽くする
古い書き方に変換する :古いブラウザでも動く形に直す
開発を速くする :保存した瞬間に画面へ反映する(ホットリロード)
Vite で始める(現在の定番) コピー
npm create vite@latest my-app
cd my-app
npm install
npm run dev # → http://localhost:5173 が立ち上がる
npm run build # → dist/ に公開用ファイルができる
Vite (ヴィート)は現在いちばん使われている開発サーバー兼ビルドツールです。少し前は webpack (ウェブパック)が主流でした。求人票にはどちらも出てきますが、考え方は同じ なので、片方を触れば他方も読めます。
コードをそろえる道具 ── ESLint と Prettier
チーム開発では必須 書き方の好みで議論する時間は、そのまま無駄になります。「道具が決めたとおりにする」 と決めてしまうのが正解です。VS Codeで「保存時に自動整形」を有効にしておくと、意識しなくてもそろいます。
TypeScript ── 「型」で事故を防ぐ
TypeScript は、JavaScriptに型の指定 を足した言語です。実行する前(書いている最中)に、型の食い違いを指摘してくれます。実務の求人では、すでにほぼ標準 と言ってよい状況です。
TypeScript の雰囲気 コピー
// JavaScript:実行してみるまで誤りに気づけない
function calcTax(price) {
return Math.round(price * 1.1);
}
calcTax("1980"); // 文字列を渡しても、その場では止まらない
// TypeScript:書いている最中に赤線で指摘される
// function calcTax(price: number): number {
// return Math.round(price * 1.1);
// }
// calcTax("1980"); ← エラー:型 'string' を型 'number' に割り当てられません
// データの形も定義できる
// type Product = { id: number; name: string; price: number; stock?: number };
// ↑ ? は「無くてもよい」
学ぶ順番 JavaScriptが書けるようになってから TypeScriptに進んでください。同時に学ぶと、「どちらの決まりごとなのか」が区別できず混乱します。目安として、このページのSTEP 13まで自力で書けるようになったら、TypeScriptに進むのが適切です。学習には1〜2週間あれば入口は越えられます。
Gitと組み合わせる
実務では、コードの変更履歴をGit で管理します。JavaScript開発では、次の内容を .gitignore に入れるのが定番です。
.gitignore の基本 コピー
node_modules/
dist/
.env
.env.local
.DS_Store
鍵を公開しない APIキーやパスワードを、コードに直接書いてGitに上げてしまう事故が後を絶ちません。
.env ファイルに分け、
必ず .gitignore に入れて ください。
そして、ブラウザで動くJavaScriptに秘密の鍵を置くことはできません (誰でも中身を見られるため)。秘密を伴う通信は、必ずサーバー側で行います。Gitの基本は
Git・GitHub入門 で扱っています。
一問一答
Node.jsとは何ですか。
node_modules/ をGitで管理しないのはなぜですか。
package-lock.json は何のためにありますか。
TypeScriptは何を解決する道具ですか。
解答を見る
ブラウザの外でJavaScriptを実行する環境。開発ツールを動かす土台にもなる。
ファイル数が膨大で、package.json から再生成できるため。
実際に入れたパッケージの版を固定し、全員の環境をそろえるため。
型の食い違いを実行前に検出し、実行時のエラーを減らす。
このステップは理解できた
STEP 16
実務
実務のコード品質 ── 安全に、壊れにくく書く
目安 5日
このステップの到達点 ── XSSを防ぐ書き方を身につけ、日付・保存・パフォーマンスの定番処理を書ける。レビューで指摘される前に自分で気づける 状態になる。ここが「勉強した人」と「仕事ができる人」の分かれ目です。
セキュリティ ── 画面側で必ず守ること
安全に表示する コピー
const comment = getUserComment(); // 利用者が書いた文字列
// ✕ 危険
box.innerHTML = `<p>${comment}</p>`;
// ◯ 安全:要素を作り、文字は textContent で入れる
const p = document.createElement("p");
p.textContent = comment;
box.replaceChildren(p);
// リンクを扱うときの注意
const a = document.createElement("a");
// javascript: で始まるURLは危険なので、http(s) だけを許可する
if (url.startsWith("https://") || url.startsWith("http://")) {
a.href = url;
} else {
a.href = "#";
}
a.rel = "noopener noreferrer"; // target="_blank" と併用する
ブラウザへの保存 ── localStorage
保存・読み出し・削除 コピー
// 文字列しか保存できない → JSONに変換する
const settings = { theme: "dark", fontSize: 16 };
localStorage.setItem("app-settings", JSON.stringify(settings));
// 読み出し(無ければ null)
const saved = localStorage.getItem("app-settings");
const parsed = saved ? JSON.parse(saved) : null;
// 実務では必ず try/catch で囲む(容量超過・privateモードで失敗する)
function loadSettings() {
try {
const raw = localStorage.getItem("app-settings");
return raw ? JSON.parse(raw) : { theme: "light", fontSize: 14 };
} catch (error) {
console.error("設定の読み込みに失敗", error);
return { theme: "light", fontSize: 14 }; // 既定値で続行する
}
}
localStorage.removeItem("app-settings"); // 1件削除
// localStorage.clear(); // 全削除(他機能も消えるので注意)
ちなみに、このページの「ふりがなON/OFF」と「進み具合」も localStorage で保存しています 。ページを閉じても設定が残るのはこの仕組みのおかげです。sessionStorage はタブを閉じると消える版です。
日付の扱い
Date の基本と注意点 コピー
const now = new Date();
console.log(now.getFullYear()); // 2026
console.log(now.getMonth()); // ★0から始まる(0 = 1月)
console.log(now.getDate()); // 日
console.log(now.getDay()); // 曜日(0 = 日曜)
// 日本語で整形する(これが最も手軽で確実)
console.log(now.toLocaleDateString("ja-JP")); // 2026/8/14
console.log(now.toLocaleString("ja-JP", {
year: "numeric", month: "long", day: "numeric", weekday: "short",
})); // 2026年8月14日(金)
// 文字列から作るときは、この形式を使う(他の形式は環境差が出る)
const d = new Date("2026-08-14T09:00:00+09:00");
// 日数を足す
const tomorrow = new Date(now);
tomorrow.setDate(now.getDate() + 1);
// 差を求める(ミリ秒で返る)
const diffDays = Math.floor((tomorrow - now) / (1000 * 60 * 60 * 24));
月は0から getMonth() は1月が 0、12月が 11 です。「1か月ずれる」不具合の定番原因です。日付の計算が複雑になる場合は、day.js などのライブラリを使う のが実務の判断です(自作すると、うるう年や時差でほぼ確実に間違えます)。
文字列と数値の実務テクニック
よく使う処理 コピー
const s = "こども経済新聞";
console.log(s.length); // 7
console.log(s.includes("経済")); // true
console.log(s.startsWith("こども")); // true
console.log(s.slice(0, 3)); // こども
console.log(s.replace("新聞", "ニュース")); // こども経済ニュース
console.log(" 余白 ".trim()); // "余白"
console.log("a,b,c".split(",")); // ["a","b","c"]
console.log("5".padStart(2, "0")); // "05"(時刻表示に便利)
// 金額の表示
console.log((1234567).toLocaleString()); // "1,234,567"
console.log(new Intl.NumberFormat("ja-JP", {
style: "currency", currency: "JPY",
}).format(1980)); // "¥1,980"
// 乱数(0以上10未満の整数)
console.log(Math.floor(Math.random() * 10));
読みやすいコードの原則
✕ 指摘されるコード
function f(d) { let r = []; for (let i = 0; i < d.length; i++) { if (d[i].s == 1) r.push(d[i]); } return r; }
名前に意味がなく、1 が何を指すかも分かりません。
◯ 通るコード
const STATUS_ACTIVE = 1; function filterActiveUsers(users) { return users.filter( (user) => user.status === STATUS_ACTIVE ); }
名前が説明になっていて、コメントが要りません。
意味のない数字(マジックナンバー)に名前を付ける :1 → STATUS_ACTIVE
コメントは「なぜ」を書く :「何をしているか」はコードを読めば分かる。書くべきは理由と背景
早期リターンで入れ子を浅く (STEP 2)
同じコードが3回出たら関数にする
1つの関数は1画面(20〜30行)に収める
パフォーマンスの基本
DOM操作は回数を減らす :ループの中で毎回 appendChild せず、まとめて追加する(STEP 8)
要素の取得を使い回す :ループの中で querySelector を呼ばない
スクロールやリサイズの処理は間引く :デバウンス/スロットル(STEP 9)
画像は遅延読み込み :<img loading="lazy">
測ってから直す :開発者ツールの Performance タブと console.time() で確認する。推測で最適化しない
動かして確かめる ── textContent と innerHTML の違い(XSS)
textContent で表示
innerHTML で表示
どちらかのボタンを押してください。
textContent はタグを文字として 表示し、innerHTML はタグとして解釈 します。もし利用者が <img src=x onerror="..."> のような文字列を入力したら、後者ではそのコードが実行されます 。このデモでは危険な要素は除去していますが、実際のコードでは利用者の入力を innerHTML に入れない のが原則です。
一問一答
XSSを防ぐために、表示処理で守るべき原則は何ですか。
localStorage に保存できるのは何型ですか。オブジェクトはどう保存しますか。
new Date().getMonth() が返す値の注意点は何ですか。
APIキーをブラウザのJavaScriptに書いてはいけないのはなぜですか。
解答を見る
利用者の入力を innerHTML に入れず、textContent で表示する。
文字列のみ。JSON.stringify() で文字列にして保存し、JSON.parse() で戻す。
0から始まる(0が1月)。そのまま表示すると1か月ずれる。
ブラウザに配られたコードは誰でも中身を見られるため、鍵が漏れる。秘密を伴う処理はサーバー側で行う。
このステップは理解できた
STEP 17
実務
次の一歩 ── フレームワークと、学び続け方
目安 3日+継続
このステップの到達点 ── フレームワークが何を解決する道具かを説明でき、次に学ぶものを自分で選べる。作品(ポートフォリオ)の方向性と、仕事に就くまでの現実的な道すじが描ける。
なぜフレームワークが要るのか
ここまでの方法(素のJavaScriptで querySelector して書き換える)は、画面が小さいうちは最良 です。しかし、状態が10個、20個と増えると破綻します。「この値が変わったら、どこを書き換えるんだったか」を人間が全部覚えることになるからです。
考え方の違い
手続き型と宣言型の違い
素のJavaScriptでは、状態が変わるたびに書き換える場所を1つずつ自分で指示します。ReactやVueでは、状態と画面の対応だけを書けば、画面が自動で追従します。
【素のJavaScript】手続き型
状態が
変わった
見出しを書き換える
一覧を作り直す
ボタンを無効にする
書き換える場所を、1つずつ自分で指示する。
画面が増えるほど、指示の書き漏らしが起きる。
【React / Vue】宣言型
状態を
変えるだけ
自動で追従
画面
「この状態のとき、画面はこうなる」とだけ書く。
どこを書き換えるかは、道具側が引き受ける。
始める時期 このページのSTEP 13まで自力で書けること が目安です。素のJavaScriptを飛ばしてフレームワークから入ると、エラーが出たときに「フレームワークの問題か、JavaScriptの問題か」が切り分けられません。急がば回れです。
作品を作る ── 何を作るか
学習の総仕上げは、小さくても完成させた作品 です。採用の場で見られるのは、規模ではなく「最後まで作り切ったか」 と「なぜそう作ったかを説明できるか」 です。
作品はGitHubで公開 し、README(説明書)を丁寧に書く ことを勧めます。「何ができるか」「工夫した点」「詰まって解決したこと」を書いておくと、面接での話題がそのまま用意できます。公開の方法はGit・GitHub入門 を参照してください。
学び続けるための情報源
MDN Web Docs :メソッドの仕様を調べるならここ。日本語版があり、正確です。検索するときは「MDN ○○」 と付けるのが定番です
公式ドキュメント :ライブラリの使い方は、まず公式を見る習慣を付けてください
エラー文で検索する :エラーメッセージをそのまま貼って検索すると、たいてい同じ問題に当たった人が見つかります
生成AIの使い方 :答えを写すのではなく、「なぜこう書くのか」を聞く 使い方をしてください。動くコードだけ受け取ると、直せない資産が増えます
仕事に就くまでの現実的な道すじ
採用の場で問われること 文法の暗記ではなく、「詰まったとき、どう調べて、どう解決したか」 です。エラーメッセージの読み方(STEP 14)、通信の切り分け(STEP 13)、入力チェックの設計(STEP 10)——この3つを自分の言葉で説明できれば、未経験からでも十分に評価されます。
まだ学んでいない、次に触れるとよいもの
テスト (Vitest、Jest):関数が期待どおり動くことを自動で確認する
状態管理 (Zustand、Piniaなど):画面をまたいだデータの持ち方
アクセシビリティ :キーボード操作、読み上げ対応。実務で求められる場面が増えています
Web API各種 :Intersection Observer(表示検知)、Web Storage、Canvas、File API
サーバー側 :Node.js+Express、データベース。1つの言語で両側を書けるのがJavaScriptの強みです
一問一答
フレームワークは、どんな問題を解決する道具ですか。
フレームワークを学び始める目安は何ですか。
作品づくりで、規模より大事なことは何ですか。
解答を見る
状態が増えたときの「画面の書き換え漏れ」。状態を書けば画面が自動で追従する形にしてくれる。
素のJavaScriptで、フォーム処理とAPI通信(このページのSTEP 13まで)が自力で書けること。
最後まで完成させることと、なぜそう作ったかを説明できること。
このステップは理解できた
条件に合うステップがありません。キーワードを変えるか、「すべて」を選んでください。