目次を開く(全 17 章)
01
JavaScript の置き方
HTML から読み込むだけです。defer を付けて <head> に置くのが今の定番。これで「HTML を全部読み終えてから実行」されるので、要素が見つからないエラーを防げます。
<head>
<script src="assets/javascripts/main.js" defer></script>
</head>
書いた結果を確かめるには console.log() を使います。ブラウザで F12(Mac は ⌘ + Option + I)を押すと開発者ツールが開き、「コンソール」タブに出力されます。これが最初にして最強のデバッグ道具です。
console.log('こんにちは、JavaScript');
console.log(1 + 2);
console.log('今日の日付:', new Date().toLocaleDateString('ja-JP'));
このページでは、コンソールの代わりに上の黒い枠へ出力しています。
02
変数 — const と let
値に名前を付けて取っておく箱です。宣言は 2 つだけ覚えます。
まず const。あとで入れ替えるものだけ let。
古い var は使いません。
const name = '山田'; // 入れ替えない
let count = 0; // あとで増える
count = count + 1;
count += 1; // 同じ意味の短い書き方
console.log(name, count);
// const でも「中身」は変えられる(箱を差し替えられないだけ)
const scores = [10, 20];
scores.push(30);
console.log(scores);
// ブロック { } の外からは見えない
{
const secret = 'ここだけ';
console.log(secret);
}
// console.log(secret); ← ここで使うとエラーになる
var はブロック { } を無視して外まで漏れ出し、同じ名前で何度でも宣言できてしまいます。
const / let はブロックの中だけで有効なので、
取り違えのバグが起きません。名前は userName のように、意味が分かる英単語で。
03
データ型
扱える値の種類です。まずはこの 7 つ。typeof で種類を調べられます。
const text = 'あいうえお'; // 文字列 string
const price = 1280; // 数値 number(整数も小数も同じ)
const isOpen = true; // 真偽値 boolean
const nothing = null; // 「空」だと明示した状態
let notSet; // undefined(まだ入れていない)
const list = ['赤', '青', '緑']; // 配列 array
const user = { name: '山田', age: 30 }; // オブジェクト object
console.log(typeof text, typeof price, typeof isOpen);
console.log(typeof nothing, typeof notSet);
console.log(list.length, list[0]);
console.log(user.name, user['age']);
// 文字列の中に値を埋め込む(テンプレートリテラル)
console.log(`${user.name}さんは${user.age}歳、商品は${price.toLocaleString()}円`);
typeof null が 'object' になるのは JavaScript の古い不具合で、直せないまま残っています。
04
演算子と比較
計算と比較の記号です。比較のうち等価判定は
必ず === と !==(型まで見るほう)を使います。
== は型を勝手に変換して比べるため、直感に反する結果になります。
| 記号 | 意味 | 例 |
|---|---|---|
+ - * / | 足す・引く・かける・割る | 3 + 4 → 7 |
% | 割った余り(偶数判定によく使う) | 10 % 3 → 1 |
** | べき乗 | 2 ** 10 → 1024 |
< > | より小さい・より大きい | 2 < 5 → true |
<= >= | 以下・以上(等しい場合を含む) | 5 >= 5 → true |
=== | 型も値も等しい | 1 === '1' → false |
!== | 型か値が違う(=== の否定) | 1 !== '1' → true |
&& || ! | かつ・または・否定 | a > 0 && a < 10 |
// 四則演算
console.log(3 + 4, 10 - 4, 6 * 7); // 7 6 42
console.log(10 / 3); // 3.333…(整数どうしでも小数になる)
console.log(10 % 3); // 1(余り)
console.log(2 ** 10); // 1024(べき乗)
// + は文字列の連結にもなる。数値と混ぜると文字列になってしまう
console.log('合計' + 100, '1' + 1, '1' - 1); // 合計100 / 11 / 0
// 大小の比較
console.log(2 < 5, 5 > 5, 5 >= 5, 5 <= 4); // true false true false
// 等しい/等しくない。型まで見るこの 2 つを使う
console.log(1 === 1, 1 === '1'); // true false
console.log(1 !== 2, 1 !== '1'); // true true
console.log(1 == '1', 1 != '1'); // true false ← 型を無視するので危険
// 組み合わせる
const age = 20;
console.log(age >= 18 && age < 65); // かつ
console.log(age < 18 || age >= 65); // または
console.log(!(age >= 18)); // 否定
// 偽とみなされる値(falsy)はこの 6 つだけ
for (const value of [false, 0, '', null, undefined, NaN]) {
console.log(value, '→', Boolean(value));
}
// ?? は「null か undefined のときだけ」右を使う
const input = 0;
console.log(input || 10); // 10 ← 0 も偽なので置き換わってしまう
console.log(input ?? 10); // 0 ← こちらが意図どおり
// ?. は途中が無ければ undefined を返す(エラーで止まらない)
const data = { user: { name: '山田' } };
console.log(data.user?.name);
console.log(data.company?.name);
05
制御構文
「条件で分ける」「繰り返す」。プログラムの流れを決める部分です。
const score = 78;
if (score >= 90) {
console.log('優');
} else if (score >= 70) {
console.log('良');
} else {
console.log('可');
}
// 短い分岐は三項演算子で 1 行に
console.log(score >= 60 ? '合格' : '不合格');
// 値で分けるなら switch
const day = 'sat';
switch (day) {
case 'sat':
case 'sun':
console.log('休み');
break; // break を忘れると次に流れ込む
default:
console.log('平日');
}
// 配列を回すなら for...of が読みやすい
for (const fruit of ['りんご', 'みかん']) {
console.log(fruit);
}
// 回数を数えるなら for
for (let i = 1; i <= 3; i++) {
if (i === 2) continue; // この回だけ飛ばす
console.log(`${i} 回目`);
}
// 番号も欲しいときは entries()
for (const [index, name] of ['A', 'B'].entries()) {
console.log(index, name);
}
06
配列とよく使うメソッド
配列は「順番に並んだ値の入れ物」です。まずは回し方を 3 つ覚えます。
for 文、for...of、そして forEach。どれも「1 つずつ順番に処理する」という同じことをしています。
const fruits = ['りんご', 'みかん', 'ぶどう'];
// 1. for 文:番号(添字)を自分で進める。いちばん基本の形
for (let i = 0; i < fruits.length; i++) {
console.log(i, fruits[i]);
}
// 2. for...of:番号が要らないなら、こちらが読みやすい
for (const fruit of fruits) {
console.log(fruit);
}
// 3. forEach:配列に「1 つずつ処理して」と頼む。2 つめの引数が番号
fruits.forEach((fruit, index) => {
console.log(`${index + 1} 番目:${fruit}`);
});
// for と for...of は途中で抜けられる
for (const fruit of fruits) {
if (fruit === 'みかん') break; // ここで終わり
console.log('見つける前:', fruit);
}
| 書き方 | 向いている場面 | 途中で抜けられる |
|---|---|---|
for | 番号を使う。逆順や 2 つ飛ばしなど、進み方を細かく決めたいとき | できる(break) |
for...of | 中身だけ順番に見たいとき。ふだんはこれ | できる(break) |
forEach | 1 つずつ処理するだけのとき。メソッドをつなげて書ける | できない |
forEach の中で break は書けず、return を書いてもその 1 回分を飛ばすだけです(continue と同じ働き)。途中で打ち切りたいときは for...of を使ってください。
名前の付いた処理に置き換える
回すことに慣れたら、次はメソッドです。「変換するなら map」「絞り込むなら filter」のように
やりたいことが名前で分かるので、コメントが要らなくなります。まずは map / filter / find / reduce の 4 つ。
const items = [
{ name: 'えんぴつ', price: 120, stock: 5 },
{ name: 'ノート', price: 280, stock: 0 },
{ name: '消しゴム', price: 90, stock: 12 }
];
// map:全部を別の形に変える(個数は変わらない)
console.log(items.map((item) => item.name));
// filter:条件に合うものだけ残す
console.log(items.filter((item) => item.stock > 0).map((i) => i.name));
// find:最初の 1 つを探す(無ければ undefined)
console.log(items.find((item) => item.price < 100));
// reduce:ひとつの値にまとめる(合計・最大など)
const total = items.reduce((sum, item) => sum + item.price, 0);
console.log('合計', total);
// some / every:ひとつでも / 全部が
console.log(items.some((i) => i.stock === 0), items.every((i) => i.price > 50));
// 元の配列を壊さない並べ替え(toSorted)
const byPrice = items.toSorted((a, b) => a.price - b.price);
console.log(byPrice.map((i) => i.price), items.map((i) => i.price));
// at(-1) で最後の要素
console.log(['春', '夏', '秋', '冬'].at(-1));
// includes / join / スプレッド
console.log(['赤', '青'].includes('青'), ['赤', '青'].join(' と '));
console.log([...[1, 2], ...[3, 4]]);
sort() は元の配列を並べ替えてしまいます。toSorted() は新しい配列を返すので、元を壊しません。
07
オブジェクト
「名前と値」の組を束ねたものです。関連するデータをひとまとめにするときに使います。
const user = { name: '山田', age: 30, city: '大阪' };
// 分割代入:必要なものだけ取り出す
const { name, city } = user;
console.log(name, city);
// 名前を変えて取り出す/既定値を付ける
const { name: userName, country = '日本' } = user;
console.log(userName, country);
// 配列も同じように取り出せる
const [first, second] = ['金', '銀'];
console.log(first, second);
// スプレッドで複製しつつ一部を上書き(元は壊さない)
const updated = { ...user, age: 31 };
console.log(updated, user.age);
// 省略記法:変数名とキー名が同じなら 1 回でよい
const age = 31;
console.log({ name, age });
// 一覧として回す
for (const [key, value] of Object.entries(user)) {
console.log(`${key}:${value}`);
}
console.log(Object.keys(user), Object.values(user));
08
関数
処理に名前を付けて、何度でも呼べるようにしたものです。書き方は 2 通りありますが、意味はほぼ同じ。使い分けの目安も添えます。
// 関数宣言。呼び出しより後ろに書いてもよい(巻き上げ)
function greet(name) {
return `${name}さん、こんにちは`;
}
// アロー関数。式なので、書いた後でしか使えない
const double = (n) => n * 2; // { } と return を省略できる
const area = (w, h) => {
const size = w * h;
return size;
};
console.log(greet('山田'), double(21), area(3, 4));
// 既定値。渡されなければこれを使う
const tax = (price, rate = 0.1) => Math.round(price * (1 + rate));
console.log(tax(1000), tax(1000, 0.08));
// 残り全部を配列で受け取る
const sum = (...numbers) => numbers.reduce((a, b) => a + b, 0);
console.log(sum(1, 2, 3, 4));
// 関数を引数に渡せる(これがコールバック)
const twice = (fn, value) => fn(fn(value));
console.log(twice(double, 5));
// 外側の変数を覚えたままの関数(クロージャ)
const makeCounter = () => {
let count = 0;
return () => ++count;
};
const next = makeCounter();
console.log(next(), next(), next());
迷ったらアロー関数で構いません。this の扱いが素直だからです。
function 宣言は「呼び出しより後ろに定義できる(巻き上げ)」ので、主役の処理を上に、部品を下に書きたいときに便利です。
オブジェクトのメソッドとして this を使いたいときは function(またはメソッド記法)を選びます。
09
クラス
「同じ形のデータ + それを扱う処理」をまとめる仕組みです。同じ種類のものをいくつも作るときに便利です。
class Cart {
#items = []; // # で始まると外から触れない(プライベート)
static taxRate = 0.1; // クラス自体に属する値
constructor(owner) { // new したときに 1 度だけ走る
this.owner = owner;
}
add(name, price) { // メソッド
this.#items.push({ name, price });
return this; // this を返すと .add().add() とつなげられる
}
get count() { // ゲッター:() を付けずに読める
return this.#items.length;
}
get total() {
const sum = this.#items.reduce((a, item) => a + item.price, 0);
return Math.round(sum * (1 + Cart.taxRate));
}
toString() {
return `${this.owner}のカート:${this.count} 点 / ${this.total} 円`;
}
}
const cart = new Cart('山田');
cart.add('えんぴつ', 120).add('ノート', 280);
console.log(cart.count, cart.total);
console.log(String(cart));
// 継承:元のクラスを受け継いで、一部だけ変える
class GiftCart extends Cart {
get total() {
return super.total + 300; // 包装代を足す
}
}
const gift = new GiftCart('鈴木');
gift.add('ノート', 280);
console.log(String(gift));
10
DOM 操作 — HTML を読み書きする
ブラウザは HTML を読み込むと、要素をたどれる木構造(DOM)に変換します。 JavaScript はこの木を探して・読んで・書き換えることでページを動かします。
下のデモは、この HTML に対して動いています。
<h3 id="title">見出し</h3>
<p class="message">ここが書き換わります</p>
<ul id="list"></ul>
<button id="paint" type="button">色を変える</button>
見出し
// 1. 探す(CSS セレクタがそのまま使える)
const title = document.querySelector('#title');
const message = document.querySelector('.message');
const list = document.querySelector('#list');
// 2. 書き換える
title.textContent = 'JavaScript で書き換えた見出し';
message.textContent = `いまは ${new Date().toLocaleTimeString('ja-JP')} です`;
// 3. 作って足す
const fruits = ['りんご', 'みかん', 'ぶどう'];
for (const fruit of fruits) {
const li = document.createElement('li');
li.textContent = fruit;
list.append(li);
}
// 4. クラスと属性
title.classList.add('is-active');
title.dataset.role = 'heading'; // data-role="heading" になる
title.style.color = '#2f80b8'; // 直接スタイルを当てることもできる
// 5. まとめて処理する
for (const li of document.querySelectorAll('#list li')) {
li.style.fontWeight = '700';
}
// ボタンの動作は次の章で
document.querySelector('#paint').addEventListener('click', () => {
message.style.backgroundColor = '#eaf2f8';
});
「色を変える」を押すと、実際に背景色が変わります。
- querySelector()
- 条件に合う最初の 1 つ。無ければ
null。 - querySelectorAll()
- 合うもの全部。
for...ofで回せます。 - textContent
- 文字を読み書きする。安全なのでまずこちら。
- innerHTML
- HTML として解釈する。外部から来た文字を入れると危険(16 章参照)。
- createElement / append
- 要素を作って、子として加える。
- classList
add/remove/toggle/contains。見た目の変更は CSS のクラス切り替えで。- dataset
data-*属性の読み書き。HTML 側に小さな情報を持たせられます。- closest()
- 自分から親をたどって、条件に合う祖先を探す。イベント処理で多用します。
11
イベント — 操作に反応する
「クリックされたら」「入力されたら」を addEventListener() で登録します。
HTML に onclick="…" と書く方法もありますが、
処理は JavaScript 側にまとめるのが今の作法です。
こんにちは、ゲストさん
// クリック
const counter = document.querySelector('[data-count]');
let count = 0;
counter.addEventListener('click', () => {
count += 1;
counter.textContent = `押した回数:${count}`;
});
// 入力のたびに反応する
const who = document.querySelector('#who');
const hello = document.querySelector('.hello');
who.addEventListener('input', (event) => {
const name = event.target.value.trim();
hello.textContent = `こんにちは、${name || 'ゲスト'}さん`;
});
// イベント委譲:親で 1 つだけ受けて、どれが押されたかを調べる
// ボタンが増えても登録し直さなくてよい
document.querySelector('.menu').addEventListener('click', (event) => {
const button = event.target.closest('button');
if (!button) return;
hello.style.color = button.dataset.color;
});
event.target… 実際に操作された要素。event.currentTargetは登録した側の要素です。event.preventDefault()… 既定の動作(リンクの移動、フォーム送信)を止めます。- よく使うイベント:
clickinputchangesubmitkeydownpointerdownscroll。 - スクロールなど大量に発火するイベントは、中で重い処理をしないこと。必要なら
requestAnimationFrameで間引きます。
12
非同期処理 — 待つ処理の書き方
通信やタイマーは「すぐには終わらない処理」です。
JavaScript は待っている間も止まらずに次へ進むので、
終わったら何をするかを書いておく必要があります。それが Promise と async / await です。
// 「あとで終わる処理」を作る
const wait = (ms, value) => new Promise((resolve) => setTimeout(() => resolve(value), ms));
console.log('1. ここが先に動く');
// async を付けた関数の中では await で「終わるまで待つ」と書ける
async function main() {
const first = await wait(10, 'お茶');
console.log('3. 届いた:', first);
// 順番に待つ必要がないものは、まとめて待つほうが速い
const [a, b] = await Promise.all([wait(10, 'パン'), wait(10, '牛乳')]);
console.log('4. まとめて:', a, b);
// 失敗するかもしれない処理は try / catch で囲む
try {
await Promise.reject(new Error('通信に失敗しました'));
} catch (error) {
console.log('5. 捕まえた:', error.message);
}
}
main();
console.log('2. 待たずにここまで進む');
出力の順番に注目してください。1 → 2 → 3 → 4 → 5 の順で出ます。
async function loadUsers() {
try {
const response = await fetch('/api/users.json');
// fetch は 404 でも例外にならない。自分で確かめる
if (!response.ok) throw new Error(`HTTP ${response.status}`);
const users = await response.json();
return users;
} catch (error) {
console.error('読み込みに失敗しました', error);
return [];
}
}
ファイルをダブルクリックして開いた状態(file://)では、セキュリティ上の制限で fetch や ES モジュールが動きません。試すときは、エディタの「ローカルサーバーで開く」機能を使うか、
npx serve のような簡易サーバーを立ててください。
13
モジュール — ファイルを分ける
コードが増えてきたら、役割ごとにファイルを分けます。
export で外に出し、import で取り込みます。
// ---- utils.js ----
export const tax = (price) => Math.round(price * 1.1);
export const format = (n) => `${n.toLocaleString()} 円`;
// ---- main.js ----
import { tax, format } from './utils.js'; // 拡張子まで書く
console.log(format(tax(1000)));
// HTML 側では type="module" を付ける
// <script type="module" src="main.js"></script>
type="module"を付けた<script>は自動的に defer になり、中身は常に strict モードで動きます。- モジュールの中の変数はそのファイルだけのもの。名前の衝突を気にしなくて済みます。
- ただし
file://では動きません(前章の注意と同じ理由)。小さなサイトなら、モジュールを使わず IIFE で包む書き方でも十分です。
14
最近入った便利な機能
JavaScript は毎年更新されています。ここ数年で入って、 もう普通に使えるものを挙げておきます。知っていると書く量がぐっと減ります。
const people = [
{ name: '山田', team: '営業' },
{ name: '鈴木', team: '開発' },
{ name: '佐藤', team: '営業' }
];
// Object.groupBy:キーごとにまとめる
console.log(Object.groupBy(people, (p) => p.team));
// 元を壊さない配列操作
const nums = [3, 1, 2];
console.log(nums.toSorted(), nums.toReversed(), nums.with(0, 99), nums);
// findLast / at
console.log(nums.findLast((n) => n < 3), nums.at(-1));
// Set の演算
const a = new Set([1, 2, 3]);
const b = new Set([2, 3, 4]);
console.log(a.union(b), a.intersection(b), a.difference(b));
// イテレータヘルパー:途中で打ち切れるので無駄がない
const evens = nums.values().filter((n) => n % 2 === 1).map((n) => n * 10).toArray();
console.log(evens);
// structuredClone:入れ子のオブジェクトも丸ごと複製
const original = { user: { name: '山田' } };
const copy = structuredClone(original);
copy.user.name = '別人';
console.log(original.user.name, copy.user.name);
// Promise.withResolvers:resolve / reject を外に取り出せる
const { promise, resolve } = Promise.withResolvers();
resolve('あとから解決できる');
promise.then((value) => console.log(value));
言語仕様は毎年 6 月に更新され、ブラウザへの搭載はそれとは別のペースで進みます。
使う前に MDN の「ブラウザーの互換性」表か、Can I use で対応状況を確認してください。
using による自動後始末や、日付時刻を扱う Temporal のように、広まりつつある段階の機能もあります。
15
動かないときの調べ方
エラーは敵ではなく、ブラウザからの案内です。 まずコンソールを開く。話はそれからです。
| よく出るエラー | たいていの原因 |
|---|---|
Cannot read properties of null | 要素が見つかっていない。セレクタの綴り違い、または defer の付け忘れ |
X is not defined | 変数名の打ち間違い、宣言し忘れ、スコープの外から使っている |
X is not a function | メソッド名の間違い(querySelectorAll を querySelector と書いた等) |
Unexpected token | 括弧や引用符の閉じ忘れ。エラーのすぐ上の行を疑う |
| 何も起きない | そもそも関数が呼ばれていない。console.log('ここ通った') を入れて確認 |
- コンソールを開く
赤い文字を読む。ファイル名と行番号が書いてあります。
- console.log を挟む
「どこまで動いたか」「変数に何が入っているか」を実際に見ます。
- 要素タブで DOM を見る
思ったクラスが付いているか、CSS のどの行が効いているかが分かります。
- 小さく切り分ける
怪しい部分だけを取り出して、単独で動かしてみます。
- それでも分からなければ検索
エラーメッセージをそのまま貼って検索。MDN の解説が出てくれば当たりです。
16
モダンな Web 制作で知っておいてほしいこと
文法とは別に、作り方の姿勢の話です。最初にこれを知っておくと、あとで作り直す回数がかなり減ります。
-
HTML → CSS → JavaScript の順で足す(段階的強化)
まず HTML だけで内容が読める状態を作り、CSS で整え、最後に JavaScript で便利にします。 JS が読み込めなくてもページが死なない構造になり、表示も速くなります。
<details>や:has()で済むことに JS を使わない、という判断もここに含まれます。 -
デバイスの幅を決め打ちしない
「iPhone は 390px だから」と個別対応を始めると終わりません。 内容が崩れる幅で切り替えるのが正解です。
clamp()やauto-fillを使えば、そもそも切り替え自体が減ります。 -
アクセシビリティは後付けできない
正しいタグ、ラベル、代替テキスト、キーボード操作、コントラスト。どれも作り終わってから足すと大工事になります。 マウスを外して Tab キーだけで操作してみるのが、いちばん手軽な自己点検です。
-
表示速度は「画像」でほぼ決まる
凝ったコードより、5MB の写真 1 枚のほうがずっと重いのが現実です。表示サイズの 2 倍までに縮める、
width/heightを書く、画面外はloading="lazy"。まずこの 3 つを守ってください。 -
innerHTML に外から来た文字を入れない
利用者の入力や URL の値をそのまま
innerHTMLに入れると、スクリプトを埋め込まれる恐れがあります(XSS)。 文字を入れるだけなら必ずtextContent。外部サイトへのリンクにはrel="noopener noreferrer"を付けます。 -
フレームワークは急がなくてよい
React や Vue は「状態が複雑なアプリ」のための道具です。数ページのサイトなら素の HTML/CSS/JS のほうが速くて壊れにくい。 土台が分かっていないと、フレームワークの不具合は直せません。
-
ファイル構成と名前を決めておく
assets/stylesheets/assets/javascripts/assets/images/のように場所を決め、ファイル名は半角小文字とハイフンで。迷う時間が消え、他人(半年後の自分を含む)が読めるようになります。 -
バージョン管理を最初から使う
Git を入れて、区切りごとに記録(コミット)します。「昨日は動いていたのに」から復帰できるようになるだけで、挑戦できる幅が変わります。 GitHub に置けば、そのまま公開(GitHub Pages)もできます。
-
ブラウザの開発者ツールを住処にする
要素タブで DOM と CSS、コンソールでエラー、ネットワークで読み込み、 Lighthouse で速度とアクセシビリティの点検。 本を読むより、ここを触るほうが早く覚えます。
-
情報源を絞る
仕様と使い方は MDN Web Docs、対応状況は Can I use。古い記事は「その時点の正解」でしかありません。
floatで段組み、var、jQuery 前提の解説を見かけたら、まず年代を確認してください。
全部を覚えてから作り始める必要はありません。 小さくても、動くものを最後まで作るほうが、確実に身につきます。分からないことはそのつど調べればよく、そのために MDN があります。
17
次は Canvas へ
HTML で構造を、CSS で見た目を、JavaScript で動きを作れるようになりました。次は、絵そのものを JavaScript で描く世界です。ここまでの文法がそのまま道具になります。
ブラウザの中に絵を描く 座標・図形・文字・アニメーション・マウス操作を、動くデモ 16 本で。その先の発展編では、時計・グラフ・お絵かきツール・15 パズルまで作ります。