フロントエンドのためのJavaScript完全ガイド¶
JavaScriptとは¶
JavaScriptは、Webページに動的な機能を追加するプログラミング言語です。ブラウザ上で動作し、ユーザーインタラクションの処理、DOM操作、非同期通信など、モダンなWebアプリケーションに不可欠な機能を提供します。
JavaScript基礎¶
変数と定数¶
// var(古い書き方、使用は推奨されない)
var oldVariable = "古い方式";
// let(再代入可能)
let userName = "太郎";
userName = "花子"; // 再代入可能
// const(再代入不可)
const API_URL = "https://api.example.com";
const userInfo = { name: "太郎", age: 25 };
// userInfo = {}; // エラー:再代入不可
userInfo.name = "花子"; // オブジェクトの中身は変更可能
データ型¶
// プリミティブ型
const number = 42;
const string = "Hello World";
const boolean = true;
const nullValue = null;
const undefinedValue = undefined;
const symbol = Symbol("unique");
const bigint = 123n;
// オブジェクト型
const array = [1, 2, 3, "four", true];
const object = {
name: "太郎",
age: 30,
hobbies: ["読書", "映画鑑賞"]
};
const func = function() { return "Hello"; };
// 型の確認
console.log(typeof number); // "number"
console.log(typeof string); // "string"
console.log(typeof boolean); // "boolean"
console.log(Array.isArray(array)); // true
関数¶
// 関数宣言
function greet(name) {
return `こんにちは、${name}さん!`;
}
// 関数式
const greetFunction = function(name) {
return `こんにちは、${name}さん!`;
};
// アロー関数
const greetArrow = (name) => {
return `こんにちは、${name}さん!`;
};
// アロー関数(短縮形)
const greetShort = name => `こんにちは、${name}さん!`;
// 複数パラメータ
const add = (a, b) => a + b;
// デフォルトパラメータ
const greetWithDefault = (name = "ゲスト") => `こんにちは、${name}さん!`;
// 使用例
console.log(greet("太郎")); // "こんにちは、太郎さん!"
console.log(add(5, 3)); // 8
console.log(greetWithDefault()); // "こんにちは、ゲストさん!"
DOM操作¶
要素の取得¶
// IDで取得
const header = document.getElementById("header");
// クラス名で取得(複数要素)
const buttons = document.getElementsByClassName("btn");
// タグ名で取得
const paragraphs = document.getElementsByTagName("p");
// CSSセレクタで取得(最初の要素)
const firstButton = document.querySelector(".btn");
const specificElement = document.querySelector("#main .content");
// CSSセレクタで取得(すべての要素)
const allButtons = document.querySelectorAll(".btn");
const allLinks = document.querySelectorAll("a[href^='https']");
要素の操作¶
// 要素の作成
const newDiv = document.createElement("div");
const newText = document.createTextNode("新しいテキスト");
// 属性の操作
const link = document.querySelector("a");
link.setAttribute("href", "https://example.com");
link.getAttribute("href"); // "https://example.com"
link.removeAttribute("target");
// クラスの操作
const element = document.querySelector(".container");
element.classList.add("active");
element.classList.remove("inactive");
element.classList.toggle("highlighted");
element.classList.contains("active"); // true/false
// テキスト内容の変更
element.textContent = "新しいテキスト";
element.innerHTML = "<strong>HTML付きテキスト</strong>";
// スタイルの変更
element.style.color = "red";
element.style.backgroundColor = "yellow";
element.style.fontSize = "16px";
要素の追加・削除¶
// 要素の追加
const parent = document.querySelector(".parent");
const child = document.createElement("div");
child.textContent = "新しい子要素";
parent.appendChild(child); // 末尾に追加
parent.insertBefore(child, parent.firstChild); // 先頭に追加
// より柔軟な挿入
parent.insertAdjacentHTML("beforeend", "<p>HTML文字列</p>");
parent.insertAdjacentElement("afterbegin", child);
// 要素の削除
child.remove(); // 要素自体を削除
parent.removeChild(child); // 親から子要素を削除
// 要素の置換
const newElement = document.createElement("span");
newElement.textContent = "置換された要素";
parent.replaceChild(newElement, child);
イベント処理¶
イベントリスナーの基本¶
// イベントリスナーの追加
const button = document.querySelector("#myButton");
// 基本的な書き方
button.addEventListener("click", function(event) {
console.log("ボタンがクリックされました!");
console.log("イベント:", event);
});
// アロー関数を使用
button.addEventListener("click", (event) => {
console.log("ボタンがクリックされました!");
});
// 名前付き関数を使用
function handleButtonClick(event) {
console.log("ボタンがクリックされました!");
// イベントのデフォルト動作を防ぐ
event.preventDefault();
// イベントの伝播を停止
event.stopPropagation();
}
button.addEventListener("click", handleButtonClick);
// イベントリスナーの削除
button.removeEventListener("click", handleButtonClick);
各種イベントの実装例¶
// フォームの処理
const form = document.querySelector("#contactForm");
const nameInput = document.querySelector("#name");
const emailInput = document.querySelector("#email");
form.addEventListener("submit", (event) => {
event.preventDefault(); // ページリロードを防ぐ
const formData = {
name: nameInput.value,
email: emailInput.value
};
// バリデーション
if (!formData.name || !formData.email) {
alert("すべての項目を入力してください");
return;
}
console.log("送信データ:", formData);
// API送信処理などを実行
});
// 入力値の即座な検証
emailInput.addEventListener("input", (event) => {
const email = event.target.value;
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (email && !emailRegex.test(email)) {
event.target.style.borderColor = "red";
} else {
event.target.style.borderColor = "green";
}
});
// キーボードイベント
document.addEventListener("keydown", (event) => {
// Escキーでモーダルを閉じる
if (event.key === "Escape") {
closeModal();
}
// Ctrl+Sで保存
if (event.ctrlKey && event.key === "s") {
event.preventDefault();
saveDocument();
}
});
// マウスイベント
const draggableElement = document.querySelector(".draggable");
draggableElement.addEventListener("mousedown", (event) => {
console.log("マウスダウン:", event.clientX, event.clientY);
});
draggableElement.addEventListener("mousemove", (event) => {
// ドラッグ処理
});
draggableElement.addEventListener("mouseup", (event) => {
console.log("マウスアップ");
});
イベント委譲(Event Delegation)¶
// 親要素にイベントリスナーを設定
const todoList = document.querySelector("#todoList");
todoList.addEventListener("click", (event) => {
// 削除ボタンがクリックされた場合
if (event.target.classList.contains("delete-btn")) {
const todoItem = event.target.closest(".todo-item");
todoItem.remove();
}
// チェックボックスがクリックされた場合
if (event.target.classList.contains("todo-checkbox")) {
const todoItem = event.target.closest(".todo-item");
todoItem.classList.toggle("completed");
}
});
// 動的に要素を追加
function addTodoItem(text) {
const todoItem = document.createElement("div");
todoItem.className = "todo-item";
todoItem.innerHTML = `
<input type="checkbox" class="todo-checkbox">
<span class="todo-text">${text}</span>
<button class="delete-btn">削除</button>
`;
todoList.appendChild(todoItem);
}
非同期処理¶
Promise と async/await¶
// Promise の基本
function fetchUserData(userId) {
return new Promise((resolve, reject) => {
// 模擬的な非同期処理
setTimeout(() => {
if (userId > 0) {
resolve({
id: userId,
name: "太郎",
email: "taro@example.com"
});
} else {
reject(new Error("無効なユーザーID"));
}
}, 1000);
});
}
// Promise の使用
fetchUserData(1)
.then(user => {
console.log("ユーザー情報:", user);
return fetchUserPosts(user.id);
})
.then(posts => {
console.log("投稿:", posts);
})
.catch(error => {
console.error("エラー:", error.message);
});
// async/await の使用
async function loadUserProfile(userId) {
try {
const user = await fetchUserData(userId);
console.log("ユーザー情報:", user);
const posts = await fetchUserPosts(user.id);
console.log("投稿:", posts);
return { user, posts };
} catch (error) {
console.error("エラー:", error.message);
throw error;
}
}
// 使用例
loadUserProfile(1)
.then(profile => {
console.log("プロファイル読み込み完了:", profile);
})
.catch(error => {
console.error("プロファイル読み込み失敗:", error);
});
Fetch API¶
// GET リクエスト
async function fetchData() {
try {
const response = await fetch("https://api.example.com/users");
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
return data;
} catch (error) {
console.error("データ取得エラー:", error);
throw error;
}
}
// POST リクエスト
async function createUser(userData) {
try {
const response = await fetch("https://api.example.com/users", {
method: "POST",
headers: {
"Content-Type": "application/json",
"Authorization": "Bearer token123"
},
body: JSON.stringify(userData)
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const result = await response.json();
return result;
} catch (error) {
console.error("ユーザー作成エラー:", error);
throw error;
}
}
// 使用例
async function handleUserCreation() {
const userData = {
name: "花子",
email: "hanako@example.com"
};
try {
const newUser = await createUser(userData);
console.log("新しいユーザー:", newUser);
// UI更新
displaySuccessMessage("ユーザーが正常に作成されました");
} catch (error) {
displayErrorMessage("ユーザーの作成に失敗しました");
}
}
配列とオブジェクトの操作¶
配列の高階関数¶
const users = [
{ id: 1, name: "太郎", age: 25, active: true },
{ id: 2, name: "花子", age: 30, active: false },
{ id: 3, name: "次郎", age: 28, active: true },
{ id: 4, name: "久美", age: 35, active: true }
];
// map - 配列の各要素を変換
const userNames = users.map(user => user.name);
console.log(userNames); // ["太郎", "花子", "次郎", "久美"]
const userDisplays = users.map(user => ({
...user,
displayName: `${user.name} (${user.age}歳)`
}));
// filter - 条件に合う要素だけを抽出
const activeUsers = users.filter(user => user.active);
const youngUsers = users.filter(user => user.age < 30);
// find - 条件に合う最初の要素を取得
const specificUser = users.find(user => user.name === "太郎");
// reduce - 配列を単一の値に集約
const totalAge = users.reduce((sum, user) => sum + user.age, 0);
const averageAge = totalAge / users.length;
// some - 条件に合う要素が存在するかチェック
const hasActiveUser = users.some(user => user.active);
// every - すべての要素が条件に合うかチェック
const allActive = users.every(user => user.active);
// sort - 配列のソート
const sortedByAge = users.sort((a, b) => a.age - b.age);
const sortedByName = users.sort((a, b) => a.name.localeCompare(b.name));
オブジェクトの操作¶
const user = {
name: "太郎",
age: 25,
email: "taro@example.com"
};
// 分割代入
const { name, age, email } = user;
console.log(name); // "太郎"
// 残りの属性を取得
const { name: userName, ...otherProps } = user;
console.log(otherProps); // { age: 25, email: "taro@example.com" }
// オブジェクトのマージ
const additionalInfo = { city: "東京", country: "日本" };
const completeUser = { ...user, ...additionalInfo };
// 動的なプロパティ名
const propertyName = "dynamicProp";
const dynamicObject = {
[propertyName]: "動的な値",
[`${propertyName}2`]: "別の動的な値"
};
// オブジェクトのキーと値を取得
const keys = Object.keys(user); // ["name", "age", "email"]
const values = Object.values(user); // ["太郎", 25, "taro@example.com"]
const entries = Object.entries(user); // [["name", "太郎"], ["age", 25], ...]
// エントリーからオブジェクトを再構築
const rebuiltUser = Object.fromEntries(entries);
実践的な実装例¶
動的なToDoリスト¶
class TodoApp {
constructor() {
this.todos = [];
this.nextId = 1;
this.init();
}
init() {
this.setupEventListeners();
this.render();
}
setupEventListeners() {
const addForm = document.querySelector("#add-todo-form");
const todoList = document.querySelector("#todo-list");
const filterButtons = document.querySelectorAll(".filter-btn");
addForm.addEventListener("submit", (e) => {
e.preventDefault();
const input = e.target.querySelector("#todo-input");
this.addTodo(input.value.trim());
input.value = "";
});
todoList.addEventListener("click", (e) => {
const todoId = parseInt(e.target.closest(".todo-item")?.dataset.id);
if (e.target.classList.contains("toggle-btn")) {
this.toggleTodo(todoId);
} else if (e.target.classList.contains("delete-btn")) {
this.deleteTodo(todoId);
}
});
filterButtons.forEach(btn => {
btn.addEventListener("click", (e) => {
const filter = e.target.dataset.filter;
this.setFilter(filter);
});
});
}
addTodo(text) {
if (!text) return;
const todo = {
id: this.nextId++,
text: text,
completed: false,
createdAt: new Date()
};
this.todos.push(todo);
this.render();
}
toggleTodo(id) {
const todo = this.todos.find(t => t.id === id);
if (todo) {
todo.completed = !todo.completed;
this.render();
}
}
deleteTodo(id) {
this.todos = this.todos.filter(t => t.id !== id);
this.render();
}
setFilter(filter) {
this.currentFilter = filter;
this.render();
// フィルターボタンのアクティブ状態を更新
document.querySelectorAll(".filter-btn").forEach(btn => {
btn.classList.toggle("active", btn.dataset.filter === filter);
});
}
getFilteredTodos() {
switch (this.currentFilter) {
case "active":
return this.todos.filter(todo => !todo.completed);
case "completed":
return this.todos.filter(todo => todo.completed);
default:
return this.todos;
}
}
render() {
const todoList = document.querySelector("#todo-list");
const filteredTodos = this.getFilteredTodos();
todoList.innerHTML = filteredTodos.map(todo => `
<div class="todo-item ${todo.completed ? 'completed' : ''}" data-id="${todo.id}">
<button class="toggle-btn">${todo.completed ? '✓' : '○'}</button>
<span class="todo-text">${this.escapeHtml(todo.text)}</span>
<button class="delete-btn">削除</button>
</div>
`).join("");
// 統計情報の更新
this.updateStats();
}
updateStats() {
const total = this.todos.length;
const completed = this.todos.filter(todo => todo.completed).length;
const active = total - completed;
document.querySelector("#stats").innerHTML = `
全体: ${total} | 完了: ${completed} | 未完了: ${active}
`;
}
escapeHtml(text) {
const div = document.createElement("div");
div.textContent = text;
return div.innerHTML;
}
}
// アプリケーションの初期化
document.addEventListener("DOMContentLoaded", () => {
new TodoApp();
});
APIデータを使用した動的コンテンツ¶
class UserManager {
constructor() {
this.users = [];
this.loading = false;
this.init();
}
async init() {
await this.loadUsers();
this.setupEventListeners();
}
async loadUsers() {
this.setLoading(true);
try {
const response = await fetch("https://jsonplaceholder.typicode.com/users");
this.users = await response.json();
this.renderUsers();
} catch (error) {
this.showError("ユーザーデータの読み込みに失敗しました");
console.error("Error loading users:", error);
} finally {
this.setLoading(false);
}
}
setupEventListeners() {
const searchInput = document.querySelector("#user-search");
const sortSelect = document.querySelector("#user-sort");
// 検索機能(デバウンス付き)
let searchTimeout;
searchInput.addEventListener("input", (e) => {
clearTimeout(searchTimeout);
searchTimeout = setTimeout(() => {
this.filterUsers(e.target.value);
}, 300);
});
// ソート機能
sortSelect.addEventListener("change", (e) => {
this.sortUsers(e.target.value);
});
// ユーザーカードのクリックイベント
document.addEventListener("click", (e) => {
if (e.target.closest(".user-card")) {
const userId = parseInt(e.target.closest(".user-card").dataset.userId);
this.showUserDetails(userId);
}
});
}
filterUsers(searchTerm) {
const filtered = this.users.filter(user =>
user.name.toLowerCase().includes(searchTerm.toLowerCase()) ||
user.email.toLowerCase().includes(searchTerm.toLowerCase())
);
this.renderUsers(filtered);
}
sortUsers(sortBy) {
let sorted = [...this.users];
switch (sortBy) {
case "name":
sorted.sort((a, b) => a.name.localeCompare(b.name));
break;
case "email":
sorted.sort((a, b) => a.email.localeCompare(b.email));
break;
case "company":
sorted.sort((a, b) => a.company.name.localeCompare(b.company.name));
break;
}
this.users = sorted;
this.renderUsers();
}
renderUsers(usersToRender = this.users) {
const container = document.querySelector("#users-container");
if (usersToRender.length === 0) {
container.innerHTML = "<p>ユーザーが見つかりません</p>";
return;
}
container.innerHTML = usersToRender.map(user => `
<div class="user-card" data-user-id="${user.id}">
<h3>${this.escapeHtml(user.name)}</h3>
<p><strong>Email:</strong> ${this.escapeHtml(user.email)}</p>
<p><strong>Company:</strong> ${this.escapeHtml(user.company.name)}</p>
<p><strong>City:</strong> ${this.escapeHtml(user.address.city)}</p>
</div>
`).join("");
}
async showUserDetails(userId) {
const user = this.users.find(u => u.id === userId);
if (!user) return;
try {
// ユーザーの投稿を取得
const postsResponse = await fetch(`https://jsonplaceholder.typicode.com/posts?userId=${userId}`);
const posts = await postsResponse.json();
// モーダルで詳細を表示
this.showModal(`
<h2>${this.escapeHtml(user.name)}</h2>
<p><strong>Email:</strong> ${this.escapeHtml(user.email)}</p>
<p><strong>Phone:</strong> ${this.escapeHtml(user.phone)}</p>
<p><strong>Website:</strong> <a href="http://${user.website}" target="_blank">${this.escapeHtml(user.website)}</a></p>
<p><strong>Company:</strong> ${this.escapeHtml(user.company.name)}</p>
<p><strong>Address:</strong> ${this.escapeHtml(user.address.street)}, ${this.escapeHtml(user.address.city)}</p>
<h3>投稿 (${posts.length}件)</h3>
<div class="posts-list">
${posts.map(post => `
<div class="post-item">
<h4>${this.escapeHtml(post.title)}</h4>
<p>${this.escapeHtml(post.body)}</p>
</div>
`).join("")}
</div>
`);
} catch (error) {
this.showError("ユーザー詳細の読み込みに失敗しました");
}
}
showModal(content) {
const modal = document.createElement("div");
modal.className = "modal";
modal.innerHTML = `
<div class="modal-content">
<span class="close">×</span>
${content}
</div>
`;
document.body.appendChild(modal);
// モーダルを閉じる処理
const closeModal = () => {
document.body.removeChild(modal);
};
modal.querySelector(".close").addEventListener("click", closeModal);
modal.addEventListener("click", (e) => {
if (e.target === modal) closeModal();
});
document.addEventListener("keydown", (e) => {
if (e.key === "Escape") closeModal();
});
}
setLoading(loading) {
this.loading = loading;
const loadingElement = document.querySelector("#loading");
loadingElement.style.display = loading ? "block" : "none";
}
showError(message) {
const errorDiv = document.createElement("div");
errorDiv.className = "error-message";
errorDiv.textContent = message;
document.body.appendChild(errorDiv);
setTimeout(() => {
if (document.body.contains(errorDiv)) {
document.body.removeChild(errorDiv);
}
}, 5000);
}
escapeHtml(text) {
const div = document.createElement("div");
div.textContent = text;
return div.innerHTML;
}
}
// アプリケーションの初期化
document.addEventListener("DOMContentLoaded", () => {
new UserManager();
});
モダンJavaScriptの機能¶
テンプレートリテラル¶
const name = "太郎";
const age = 25;
// 基本的な使用
const message = `こんにちは、${name}さん!あなたは${age}歳ですね。`;
// 複数行文字列
const htmlTemplate = `
<div class="user-card">
<h2>${name}</h2>
<p>年齢: ${age}歳</p>
<p>成人: ${age >= 20 ? "はい" : "いいえ"}</p>
</div>
`;
// タグ付きテンプレートリテラル
function highlight(strings, ...values) {
return strings.reduce((result, string, i) => {
const value = values[i] ? `<mark>${values[i]}</mark>` : "";
return result + string + value;
}, "");
}
const highlighted = highlight`名前は${name}で、年齢は${age}歳です。`;
分割代入¶
// 配列の分割代入
const colors = ["red", "green", "blue", "yellow"];
const [primary, secondary, ...others] = colors;
console.log(primary); // "red"
console.log(secondary); // "green"
console.log(others); // ["blue", "yellow"]
// オブジェクトの分割代入
const user = {
name: "太郎",
age: 25,
email: "taro@example.com",
address: {
city: "東京",
country: "日本"
}
};
const { name, age, email, ...userInfo } = user;
const { address: { city, country } } = user; // ネストした分割代入
// 関数のパラメータで分割代入
function greetUser({ name, age = 0 }) {
return `こんにちは、${name}さん!年齢は${age}歳ですね。`;
}
console.log(greetUser({ name: "花子", age: 30 }));
クラスとモジュール¶
// クラスの定義
class User {
constructor(name, email) {
this.name = name;
this.email = email;
this.createdAt = new Date();
}
// インスタンスメソッド
greet() {
return `こんにちは、${this.name}さん!`;
}
// ゲッター
get displayName() {
return `${this.name} <${this.email}>`;
}
// セッター
set email(newEmail) {
if (this.isValidEmail(newEmail)) {
this._email = newEmail;
} else {
throw new Error("無効なメールアドレスです");
}