HTMLで骨組みを作り、CSSで見た目を整えたら、次はJavaScriptで動きをつける番です。
この記事では、フロントエンドエンジニアの学習ロードマップの4番目にあたるJavaScriptについて、基礎文法からDOM操作、非同期処理までを一通りまとめます。分量があるので、必要なところだけ拾い読みしてもらっても構いません。
JavaScriptとは
JavaScriptはWeb開発の鍵となるプログラミング言語で、フロントエンド開発では必須の技術です。クリックや入力に応じて画面が変化するなど、Webサイトにインタラクティブな動きを加えることができます。
さらにWebブラウザだけでなく、サーバーサイド(Node.js)やデスクトップアプリでも動作し、柔軟性が高く常に進化しているため、あらゆるWeb開発で人気があります。
JavaScriptの特徴
- ブラウザ上で直接実行できる
- DOM操作やイベント処理に強い
- ES6以降の新機能でモダンな書き方が可能
- フロントエンドだけでなく、Node.jsでサーバーサイド開発も可能
基礎文法
初心者が最初に学ぶべきは変数・定数、データ型、演算子、条件分岐、ループ、関数です。これらの基礎を押さえることで、応用や実践開発にもスムーズに進めます。
変数と定数
値を保存して後から参照するために「変数」や「定数」を使います。特によく使うのが let と const です。
let:値を変更できる変数
宣言後に値を上書きできます。値が変化する可能性があるときに使い、再宣言(同じ名前で再び宣言すること)はできません。
let count = 1;
count = 2; // OK: 値の上書き
console.log(count); // 2
カウンターや状態を変化させたいとき、ループ処理で使う変数などが向いています。
const:値を変更できない定数
宣言時に必ず初期値を設定する必要があり、値の再代入はできません。ただし配列やオブジェクトを入れた場合、中身の変更は可能です(変数そのものを別の値にはできない、という意味です)。
const PI = 3.14;
// PI = 3.1415; // エラー:再代入不可
const fruits = ['apple', 'banana'];
fruits.push('orange'); // OK: 中身の変更
console.log(fruits); // ['apple', 'banana', 'orange']
基本は const を使い、値を変える必要がある場合だけ let にします。こうしておくと、意図しない値の変更を防げるうえ、コードを読む人が「この変数は途中で変わらない」と判断できるようになります。チーム開発や大規模なプロジェクトでは const 中心に書くのが一般的です。
データ型
文字列(String)
文字や文章を扱う型です。'(シングルクォート)や "(ダブルクォート)、または `(バッククォート)で囲みます。
let name = "Taro";
let message = 'Hello, World!';
let template = `こんにちは、${name}さん!`; // テンプレートリテラル
テンプレートリテラル(`...${変数}...`)を使うと、変数や式を埋め込みやすくなります。文字列は + 演算子で連結もできます。
let fullName = "Taro" + " " + "Yamada";
console.log(fullName); // "Taro Yamada"
数値(Number)
整数や小数を表す型です。JavaScriptでは整数型と小数型の区別はなく、すべてNumber型として扱われます。四則演算が可能で、NaN(Not a Number)という特殊な値もNumber型です。
let age = 25;
let price = 99.99;
console.log(10 + 5); // 15
console.log("abc" * 3); // NaN
配列(Array)
複数の値を1つにまとめて管理する型です。要素は角括弧 [ ] で囲み、カンマ区切りで並べます。インデックス(位置番号)は0から始まります。
let fruits = ["apple", "banana", "orange"];
console.log(fruits[0]); // "apple"
fruits.push("grape");
console.log(fruits.length); // 4
オブジェクト(Object)
複数のデータを「名前(キー)」と「値」でまとめた型です。波括弧 { } で囲み、キー: 値 のペアで記述します。ネスト(入れ子)構造も可能です。
let person = {
name: "Taro",
age: 25,
hobby: "soccer"
};
console.log(person.name); // "Taro"
console.log(person["hobby"]); // "soccer"
let user = {
name: "Hanako",
address: {
city: "Tokyo",
zip: "100-0001"
}
};
console.log(user.address.city); // "Tokyo"
| データ型 | 記述例 | 特徴 |
|---|---|---|
| 文字列(String) | "Hello" |
文章や文字を扱う |
| 数値(Number) | 100, 3.14 |
整数・小数どちらもNumber型 |
| 配列(Array) | ["a", "b"] |
順番で管理するデータの集まり |
| オブジェクト(Object) | {key: value} |
名前で管理するデータの集まり |
配列が「順番でアクセスするデータの集まり」なのに対し、オブジェクトは「名前でアクセスするデータの集まり」だと考えると区別しやすくなります。
演算子
算術演算子
数値や変数の計算に使います。
| 演算子 | 意味 | 例 | 結果 |
|---|---|---|---|
+ |
足し算 | 5 + 3 |
8 |
- |
引き算 | 5 - 3 |
2 |
* |
掛け算 | 5 * 3 |
15 |
/ |
割り算 | 6 / 3 |
2 |
% |
余り | 7 % 3 |
1 |
** |
累乗 | 2 ** 3 |
8 |
+ は文字列でも使えます("Hello" + "World" → "HelloWorld")。
代入演算子
変数に値を代入するときに使います。
| 演算子 | 意味 | 例 | 結果 |
|---|---|---|---|
= |
値を代入 | let x = 5; |
x に 5 が入る |
+= |
足して代入 | x += 3; |
x = x + 3 |
-= |
引いて代入 | x -= 3; |
x = x - 3 |
*= |
掛けて代入 | x *= 3; |
x = x * 3 |
/= |
割って代入 | x /= 3; |
x = x / 3 |
比較演算子
値を比較して、true または false を返します。
| 演算子 | 意味 | 例 | 結果 |
|---|---|---|---|
== |
値が等しい(型は無視) | 5 == "5" |
true |
=== |
値と型が等しい | 5 === "5" |
false |
!= |
値が異なる(型は無視) | 5 != "5" |
false |
!== |
値または型が異なる | 5 !== "5" |
true |
> |
より大きい | 7 > 5 |
true |
< |
より小さい | 3 < 5 |
true |
>= |
以上 | 5 >= 5 |
true |
<= |
以下 | 4 <= 5 |
true |
比較には基本的に === を使ってください。== は型を自動変換してから比較するため、5 == "5" が true になるなど、意図しない結果を生みます。型の違いによるバグは原因を追いにくいので、最初から === で統一しておくのが安全です。
論理演算子
複数の条件を組み合わせるときに使います。
| 演算子 | 意味 | 例 | 結果 |
|---|---|---|---|
&& |
AND(かつ) | (5 > 3) && (8 > 6) |
true |
|| |
OR(または) | (5 > 10) || (8 > 6) |
true |
! |
NOT(否定) | !(5 > 3) |
false |
その他の演算子
三項演算子(条件式 ? 真の値 : 偽の値)を使うと、短い条件分岐を1行で書けます。
let age = 20;
let message = (age >= 18) ? "成人です" : "未成年です";
console.log(message); // 成人です
typeof はデータ型を調べる演算子です。
typeof "Hello" // "string"
typeof 42 // "number"
条件分岐(if / switch)
if文
「もし〜なら…する」という条件を設定するための、最も基本的な書き方です。
if (条件) {
// 条件がtrueのとき実行される処理
} else if (別の条件) {
// 上の条件がfalseで、この条件がtrueのとき実行
} else {
// どの条件も満たさなかったときに実行
}
switch文
1つの変数や値に対して、複数の候補から分岐を選ぶ場合に便利です。特に値が完全一致する場合の分岐で使います。
switch (値) {
case 値1:
// 値が値1と一致したときの処理
break;
case 値2:
// 値が値2と一致したときの処理
break;
default:
// どのcaseにも一致しないときの処理
}
使い分けとしては、条件式が範囲比較や複雑な論理式になる場合(if (age >= 18 && age < 65) など)はif文、値が複数の候補の中から完全一致で選ばれる場合(switch (color) { case "red": ... } など)はswitch文が向いています。
ループ処理(for / while / forEach)
for文
決まった回数の繰り返しに向いているループです。初期化、条件式、更新の3つを指定して使います。
for (let i = 0; i < 5; i++) {
console.log(i); // 0,1,2,3,4 が順に出力
}
- 初期化:
let i = 0→ カウンタ変数を設定 - 条件式:
i < 5→ 条件がtrueの間、処理を続ける - 更新:
i++→ 1ずつ増やす
while文
条件がtrueである限り繰り返すループです。繰り返す回数が決まっていない場合に便利です。条件式はループの先頭で評価されるため、最初にfalseなら1回も実行されません。
let count = 0;
while (count < 3) {
console.log(count); // 0,1,2
count++;
}
forEachメソッド
配列専用のループ処理です。配列の各要素に対して順番に処理を行います。コードが簡潔になり、可読性が高いのが特徴です。
const fruits = ["apple", "banana", "orange"];
fruits.forEach(function(fruit, index) {
console.log(index, fruit);
});
// 0 apple
// 1 banana
// 2 orange
第一引数が配列の要素、第二引数が要素のインデックス番号です。アロー関数でも書けます。
fruits.forEach((fruit, i) => console.log(i, fruit));
| 構文 | 向いている場面 |
|---|---|
| for文 | 回数が決まっている処理 |
| while文 | 条件がtrueの間、繰り返す処理 |
| forEach | 配列の要素を順番に処理する |
関数の定義と呼び出し
関数は、特定の処理をまとめて再利用できるようにする仕組みです。同じ処理を何度も書くのではなく関数として定義しておくことで、コードの効率化・可読性の向上・保守性の改善ができます。
関数宣言(Function Declaration)
もっとも基本的な定義方法です。定義より前に呼び出しても動作します(ホイスティングされるため)。
function greet(name) {
console.log(`こんにちは、${name}さん!`);
}
関数式(Function Expression)
関数を変数に代入して定義します。代入より前に呼び出すとエラーになります。
const greet = function(name) {
console.log(`こんにちは、${name}さん!`);
};
アロー関数(Arrow Function)
短い記法で書けるモダンな定義方法です(ES6以降)。より簡潔に書けますが、this の挙動が通常の関数と異なるため、クラスやイベントでの使い方には注意が必要です。
const greet = (name) => {
console.log(`こんにちは、${name}さん!`);
};
| 定義方法 | ホイスティング | 主な用途 |
|---|---|---|
| 関数宣言 | される | 基本的な場面、上部にまとめて書きたいとき |
| 関数式 | されない | 条件に応じて関数を切り替えるとき |
| アロー関数 | されない | 短い処理、コールバック関数 |
呼び出しと戻り値
定義した関数は 関数名(引数) という形で呼び出します。return キーワードを使うと関数から値を返せます。return を使わない場合、関数は undefined を返します。
function add(a, b) {
return a + b;
}
const result = add(3, 5);
console.log(result); // 8
引数にはデフォルト値を設定でき、省略したときに自動的に適用されます。
function greet(name = "ゲスト") {
console.log(`こんにちは、${name}さん!`);
}
greet(); // こんにちは、ゲストさん!
スコープとクロージャ
「変数がどこまで使えるか」「関数が変数をどう保持するか」に関わる仕組みです。
グローバルスコープ
プログラム全体からアクセスできる変数です。どこからでも書き換えられるため、予期せぬバグの原因になることがあります。
const globalVar = "グローバル";
function show() {
console.log(globalVar); // どこからでも参照できる
}
show(); // "グローバル"
ローカルスコープ(関数スコープ)
関数の中で定義された変数は、その関数の外からは使えません。
function test() {
const localVar = "ローカル";
console.log(localVar); // OK
}
test();
console.log(localVar); // エラー: localVar is not defined
ブロックスコープ(let / const)
let や const で宣言した変数は {}(ブロック)内だけで有効です。
if (true) {
let x = 10;
const y = 20;
console.log(x, y); // 10 20
}
console.log(x); // エラー: x is not defined
一方で var はブロックスコープを無視して関数スコープになるため、予期しない挙動を引き起こすことがあります。これが let / const を使うべき理由のひとつです。
クロージャ(Closure)
クロージャとは、関数が定義されたときのスコープを保持し続ける仕組みのことです。
function createCounter() {
let count = 0; // 外から直接アクセスできない「プライベート変数」
return function() {
count++;
return count;
};
}
const counter = createCounter();
console.log(counter()); // 1
console.log(counter()); // 2
console.log(counter()); // 3
createCounter の中の count は本来スコープの外から見えません。しかし return した関数が count を閉じ込めて保持しているため、呼び出すたびに count が増えていきます。
クロージャが役立つのは、グローバル変数を使わずに内部だけで変数を管理したいとき(カプセル化)、関数を呼び出すたびに前回の値を記録しておきたいとき(状態の保持)、そして設定を保持した関数を作りたいときです。
function createMultiplier(multiplier) {
return function(num) {
return num * multiplier;
};
}
const double = createMultiplier(2);
console.log(double(5)); // 10
const triple = createMultiplier(3);
console.log(triple(5)); // 15
配列・オブジェクト操作
配列とオブジェクトを効率的に操作することで、複雑な処理をシンプルに書けるようになります。
配列操作の基本メソッド
map
配列の各要素を変換して、新しい配列を作ります。元の配列は変更されません。
const numbers = [1, 2, 3, 4];
const doubled = numbers.map(num => num * 2);
console.log(doubled); // [2, 4, 6, 8]
filter
条件に合う要素だけを抽出して、新しい配列を作ります。
const numbers = [1, 2, 3, 4, 5];
const even = numbers.filter(num => num % 2 === 0);
console.log(even); // [2, 4]
reduce
配列の要素をひとつの値にまとめます。合計値や最大値の算出、オブジェクトの作成などに使います。
const numbers = [1, 2, 3, 4, 5];
const sum = numbers.reduce((acc, current) => acc + current, 0);
console.log(sum); // 15
| メソッド | 役割 |
|---|---|
map |
要素を変換して新しい配列を作る |
filter |
条件に合う要素だけ抽出する |
reduce |
配列をひとつの値にまとめる |
オブジェクト操作の基本
オブジェクトは「キーと値」の組み合わせでデータを管理します。
const user = {
name: "Taro",
age: 25,
job: "Engineer"
};
console.log(user.name); // "Taro"
console.log(user["age"]); // 25
スプレッド構文を使うと、既存のオブジェクトを展開して新しいオブジェクトを作れます。
const user = { name: "Taro", age: 25 };
const newUser = { ...user, job: "Engineer" };
console.log(newUser); // { name: "Taro", age: 25, job: "Engineer" }
分割代入を使うと、オブジェクトのプロパティを変数に取り出せます。
const user = { name: "Taro", age: 25 };
const { name, age } = user;
console.log(name); // "Taro"
console.log(age); // 25
配列とオブジェクトの組み合わせ
現実の開発では、配列の中にオブジェクトが入っているデータ構造を扱うことが多くなります。
const users = [
{ name: "Taro", age: 25 },
{ name: "Hanako", age: 30 },
{ name: "Jiro", age: 20 }
];
// 30歳以上のユーザーだけ抽出
const adults = users.filter(user => user.age >= 30);
console.log(adults); // [{ name: "Hanako", age: 30 }]
// 名前のリストを作成
const names = users.map(user => user.name);
console.log(names); // ["Taro", "Hanako", "Jiro"]
// 年齢の合計
const totalAge = users.reduce((sum, user) => sum + user.age, 0);
console.log(totalAge); // 75
APIから受け取るデータはたいていこの形なので、map / filter / reduce の3つが使えるようになると、実務での書ける範囲が一気に広がります。
DOM操作の基本
DOM(Document Object Model)とは、Webページをツリー構造として表現したものです。ページ内のHTML要素をオブジェクトとして扱えるため、JavaScriptから取得・変更・追加・削除ができます。
要素の取得
// idで取得
const title = document.getElementById("main-title");
// クラス名で取得(複数の場合は配列風オブジェクトで返る)
const items = document.getElementsByClassName("list-item");
// タグ名で取得
const paragraphs = document.getElementsByTagName("p");
// CSSセレクタで取得(最初の1つだけ)
const firstItem = document.querySelector(".list-item");
// CSSセレクタで取得(すべて)
const allItems = document.querySelectorAll(".list-item");
おすすめは querySelector と querySelectorAll です。CSSセレクタをそのまま使えるので柔軟性が高く、モダンな開発では主流になっています。
要素の内容を変更
const title = document.getElementById("main-title");
// テキストを変更
title.textContent = "こんにちは、JavaScript!";
// HTMLを変更(タグを含めて反映)
title.innerHTML = "<span style='color:red'>赤文字</span>";
innerHTML はHTMLタグを解釈するため便利ですが、ユーザーが入力した値をそのまま渡すとスクリプトを埋め込まれる危険があります(XSS)。表示したいのが文字だけなら、必ず textContent を使ってください。
属性の操作
HTML要素の属性(id、src、href など)を変更できます。
const link = document.querySelector("a");
// 属性を取得
console.log(link.getAttribute("href"));
// 属性を変更
link.setAttribute("href", "https://example.com");
// 直接プロパティを変更する方法もある
link.href = "https://example.org";
スタイルの変更
CSSをJavaScriptから操作できます。
const box = document.querySelector(".box");
// インラインスタイルを変更
box.style.color = "blue";
box.style.backgroundColor = "yellow";
// クラスを付け替えてスタイルを変更するのが一般的
box.classList.add("active"); // クラスを追加
box.classList.remove("hidden"); // クラスを削除
box.classList.toggle("dark"); // ON/OFF切り替え
スタイルを直接書き込むより、CSSクラスを切り替えるほうが保守性が高くなります。見た目の定義はCSS側に残しておけるので、classList を使うのがベストプラクティスです。
要素の追加・削除
// 新しい要素を作成
const newItem = document.createElement("li");
newItem.textContent = "新しい項目";
// 親要素に追加
const list = document.querySelector("ul");
list.appendChild(newItem);
// 先頭に追加
list.prepend(newItem);
// 要素を削除
list.removeChild(list.firstElementChild);
// 直接削除する方法(モダン)
newItem.remove();
イベント処理
ユーザーの操作(クリック・入力・スクロールなど)やブラウザの動作に応じて発生するのがイベントです。このイベントに対して処理を実行する仕組みをイベント処理(イベントハンドリング)といいます。
よく使われるイベント
| 分類 | イベント名 |
|---|---|
| マウス | click、dblclick、mouseover、mouseout |
| キーボード | keydown(押した瞬間)、keyup(離した瞬間) |
| フォーム | input、change、submit |
| その他 | scroll、resize、load |
イベントの登録方法
HTMLに直接書く方法もありますが、コードの分離ができず保守性が低いため、実務ではほとんど使いません。
<button onclick="alert('クリックされました!')">クリック</button>
プロパティに代入する方法は簡単ですが、1つのイベントにつき1つの処理しか登録できません。
const btn = document.querySelector("#myBtn");
btn.onclick = function() {
alert("ボタンがクリックされました!");
};
推奨されるのは addEventListener です。複数の処理を登録でき、最もよく使われる方法です。
const btn = document.querySelector("#myBtn");
btn.addEventListener("click", () => {
alert("クリックイベント1");
});
btn.addEventListener("click", () => {
alert("クリックイベント2");
});
イベントオブジェクト
イベント発生時には「どの要素で」「どんな操作があったのか」を表すイベントオブジェクトが渡されます。
const input = document.querySelector("#username");
input.addEventListener("input", (event) => {
console.log("入力された文字:", event.target.value);
});
event.target はイベントが起きた要素、event.type はイベントの種類、event.key はキー入力時に押されたキーを表します。
イベントの伝播
イベントはツリー構造を持つDOMの中を伝播します。初期設定では子要素から親要素へ伝わる「バブリング」が起こります(逆方向に伝わる「キャプチャリング」もあります)。
document.querySelector("#parent").addEventListener("click", () => {
console.log("親がクリックされた");
});
document.querySelector("#child").addEventListener("click", () => {
console.log("子がクリックされた");
});
子要素をクリックすると、子 → 親 の順に処理されます。
デフォルト動作のキャンセル
フォーム送信やリンククリックなどにはブラウザの標準動作があります。それを止めたい場合は preventDefault() を使います。
const form = document.querySelector("form");
form.addEventListener("submit", (event) => {
event.preventDefault(); // ページリロードを防ぐ
console.log("フォーム送信がキャンセルされました");
});
非同期処理の基本
JavaScriptはシングルスレッド(1つの処理しか同時に実行できない)です。サーバーからのデータ取得(API通信)、画像やファイルの読み込み、タイマー処理などでは非同期処理が必要になります。
もし非同期処理がなければ、重い処理が終わるまで他の処理が止まってしまい、ブラウザがフリーズしてしまいます。
コールバック関数
昔は非同期処理にコールバック関数を使っていました。
setTimeout(() => {
console.log("1秒後に実行されます");
}, 1000);
console.log("先にこちらが実行されます");
シンプルな処理なら問題ありませんが、非同期処理を順番に繋げようとするとネストがどんどん深くなり、いわゆる「コールバック地獄」が発生しました。
Promise
Promiseは、非同期処理の結果(成功 / 失敗)をオブジェクトとして扱える仕組みです。then で成功時の処理を、catch でエラー処理を記述します。
const promise = new Promise((resolve, reject) => {
let success = true;
if (success) {
resolve("成功しました!");
} else {
reject("失敗しました…");
}
});
promise
.then(result => console.log(result)) // 成功時の処理
.catch(error => console.error(error)); // 失敗時の処理
async / await
ES2017(ES8)から導入された構文で、非同期処理を同期処理のように書けるのが特徴です。
function fetchData() {
return new Promise(resolve => {
setTimeout(() => resolve("データ取得完了!"), 1000);
});
}
async function getData() {
console.log("データ取得中…");
const result = await fetchData(); // Promiseが解決されるまで待機
console.log(result);
}
getData();
await を使うことでコードが上から下へ読めるようになり、コールバック地獄を回避できます。
実用例:API通信
実際の開発では fetch を使ったAPI通信が多くなります。try / catch でエラー処理を行うのがポイントです。
async function fetchUsers() {
try {
const response = await fetch("https://jsonplaceholder.typicode.com/users");
const data = await response.json();
console.log(data);
} catch (error) {
console.error("エラーが発生しました:", error);
}
}
fetchUsers();
| 書き方 | 特徴 |
|---|---|
| コールバック | 古い方法。ネストが深くなりやすい |
| Promise | 成功 / 失敗を扱いやすい |
| async / await | 最も読みやすくモダンな書き方 |
よくある質問
letとconstはどちらを使えばいいですか?
まず const で書き、値を変更する必要が出たときだけ let に変える、という順番がおすすめです。こうすると「この変数は途中で変わらない」ことがコードから読み取れるようになり、意図しない書き換えによるバグも防げます。
==と===の違いは何ですか?
== は型を自動変換してから値を比較するため、5 == "5" が true になります。=== は型も含めて比較するので 5 === "5" は false です。自動変換のルールは直感に反する場面があり、バグの原因を追いにくいため、基本は === を使ってください。
varは使わないほうがいいですか?
新しく書くコードでは使う理由がほとんどありません。var はブロックスコープを無視して関数スコープになるため、if や for のブロックの外からも参照できてしまい、予期しない挙動につながります。古いコードを読むときのために、そういう仕様だと知っておけば十分です。
Promiseとasync/awaitはどちらを覚えるべきですか?
両方必要ですが、まず書き方として身につけるなら async / await です。ただし async / await はPromiseの上に成り立つ構文なので、Promiseの仕組み自体を理解していないとエラー処理でつまずきます。await を使いつつ、裏で何が起きているかはPromiseで理解する、という順番が結局は近道です。
まとめ
JavaScriptは基礎を理解すれば応用もスムーズに学べる言語です。ロードマップに沿って学習し、小規模なアプリを作ってみることで、フロントエンド開発に必要なスキルを効率的に習得できます。
分量が多く感じるかもしれませんが、最初から全部を覚える必要はありません。変数・データ型・条件分岐・関数まで押さえたら、あとは実際に手を動かしながら、必要になった項目をこのページで確認してもらえればと思います。
