JavaScriptとは
JavaScriptは、Webページへ動きや機能を加えられるプログラミング言語です。HTMLがページの構造、CSSが見た目を担当し、JavaScriptは利用者の操作に応じて内容を変更します。
- ボタンやメニューをクリックしたときの表示切り替え
- フォームへ入力された値の確認
- 画像ギャラリーやアニメーションの制御
- サーバーからデータを取得して画面を更新
- ブラウザーへの簡単なデータ保存
JavaScriptはブラウザーだけでなくNode.jsなどでも動きます。この講座では、まずブラウザー上でHTMLを操作するJavaScriptを学びます。
JavaScriptを実行してみよう
同じフォルダーに index.html と app.js を作ります。HTMLからJavaScriptファイルを読み込み、開発者ツールのコンソールへ文字を表示してみましょう。
<!doctype html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>JavaScriptの練習</title>
<script src="app.js" defer></script>
</head>
<body>
<h1>JavaScriptの練習</h1>
</body>
</html>
console.log('こんにちは、JavaScript!');
ブラウザーでページを開き、開発者ツールの「Console」に「こんにちは、JavaScript!」と表示されたら成功です。多くのブラウザーでは、ページ上で右クリックして「検証」を選ぶか、F12キーで開発者ツールを開けます。
defer を付けると、HTMLの解析を妨げずにJavaScriptを読み込み、HTMLの解析が終わってから実行します。後で扱うDOM要素を、読み込み前に探してしまう失敗を避けやすくなります。基本の書き方
文と大文字・小文字
JavaScriptは大文字と小文字を区別します。userName と username は別の名前です。文末のセミコロンは省略できる場合もありますが、この講座では区切りを分かりやすくするために付けます。
console.log('1行目');
console.log('2行目');
コメント
コメントは実行されないメモです。コードの内容をそのまま書き直すのではなく、処理の目的や、その書き方を選んだ理由を残すと役立ちます。
// 1行のコメント
/*
複数行のコメント
*/
文字列
文字列はシングルクォート、ダブルクォート、バッククォートで囲めます。バッククォートを使うテンプレートリテラルでは、${} の中へ値を埋め込めます。
const name = 'Mity';
const message = `こんにちは、${name}さん!`;
console.log(message); // こんにちは、Mityさん!
変数とデータ型
変数は値に名前を付けて保存する仕組みです。再代入しない値は const、後から別の値を代入する必要がある場合だけ let を使います。
const userName = 'Mity'; // 再代入しない
let score = 70; // 後で変更する
score = 85;
console.log(`${userName}さんの点数は${score}点です`);
var も見かけますが、スコープなどの挙動が let と異なります。新しく書くコードでは、まず const と let を使い分けましょう。基本的なデータ型
| 型 | 例 | 用途 |
|---|---|---|
| string | 'こんにちは' | 文字列 |
| number | 20、3.14 | 数値 |
| boolean | true、false | 真偽 |
| undefined | let value; | 値がまだ代入されていない |
| null | const result = null; | 値がないことを意図的に表す |
| bigint | 123n | 非常に大きな整数 |
typeof 演算子を使うと、値の型を文字列で確認できます。
const price = 500;
const label = '価格';
console.log(typeof price); // number
console.log(typeof label); // string
const は変数への再代入を禁止します。配列やオブジェクト自体の中身まで変更できなくなるわけではありません。この違いは配列の章で確認します。演算子と型変換
演算子を使うと計算、比較、条件の組み合わせができます。
| 種類 | 演算子 | 例 |
|---|---|---|
| 算術 | + - * / % ** | price * count |
| 比較 | === !== > < >= <= | age >= 18 |
| 論理 | && || ! | isMember && hasTicket |
| 代入 | = += -= *= /= | total += 100 |
| 値がない場合 | ?? | name ?? 'ゲスト' |
const price = 500;
const count = 3;
const total = price * count;
console.log(`合計は${total}円です`); // 合計は1500円です
===で比較する
=== は値と型の両方を比較します。== は比較前に型を暗黙変換するため、予想しにくい結果になることがあります。
console.log(5 === '5'); // false(数値と文字列)
console.log(5 == '5'); // true(比較前に型変換される)
入力値を数値へ変換する
フォームの value は、数字を入力しても文字列です。計算前に Number() で変換し、Number.isNaN() で変換できたか確認します。
const inputValue = '120'; // 実際にはフォームなどから受け取る
const price = Number(inputValue);
if (Number.isNaN(price)) {
console.log('数値を入力してください');
} else {
console.log(price * 2); // 240
}
'10' + 5 の結果は数値の15ではなく文字列の'105'です。入力値を計算するときは型を確認しましょう。条件分岐
if を使うと、条件に応じて処理を切り替えられます。条件は上から順番に判定され、最初に一致した処理だけが実行されます。
const score = 82;
if (score >= 80) {
console.log('合格です。よくできました!');
} else if (score >= 60) {
console.log('合格です');
} else {
console.log('もう一度挑戦しましょう');
}
ひとつの値を複数の候補と比較する場合は switch も使えます。
const day = 1;
switch (day) {
case 1:
console.log('月曜日');
break;
case 2:
console.log('火曜日');
break;
default:
console.log('その他');
}
switch で break を省くと、次の case の処理も続けて実行されます。意図的な場合を除き、各処理の最後に書きます。繰り返し
回数が分かっている処理には for、条件を満たす間繰り返す処理には while、配列の各要素には for...of が使えます。
for (let i = 1; i <= 5; i += 1) {
console.log(`${i}回目`);
}
let count = 3;
while (count > 0) {
console.log(count);
count -= 1;
}
console.log('スタート!');
const fruits = ['りんご', 'みかん', 'ぶどう'];
for (const fruit of fruits) {
console.log(fruit);
}
while の条件が永遠に true のままだと処理が終わりません。条件に使う値がループ内で変化することを確認してください。配列
配列には複数の値を順番に保存できます。最初の要素の番号は0です。
const fruits = ['りんご', 'みかん', 'ぶどう'];
console.log(fruits[0]); // りんご
console.log(fruits.length); // 3
fruits.push('もも');
console.log(fruits);
const で宣言した配列でも push() で要素を追加できます。ただし、fruits = [] のように別の配列を再代入することはできません。
配列を変換・抽出する
map() は各要素を変換した新しい配列、filter() は条件に一致した要素だけの新しい配列を返します。元の配列は変更されません。
const scores = [72, 95, 48, 81];
const doubled = scores.map((score) => score * 2);
const passed = scores.filter((score) => score >= 60);
console.log(doubled); // [144, 190, 96, 162]
console.log(passed); // [72, 95, 81]
console.log(scores); // [72, 95, 48, 81]
push()、pop()、sort() などは元の配列を変更します。元の順番を残したい場合は、const sorted = [...scores].sort((a, b) => a - b); のようにコピーしてから並べ替えます。オブジェクトとクラス
オブジェクトは、関連する値をプロパティ名と組み合わせて保存します。利用者や商品など、複数の項目を持つデータを表すときに便利です。
const user = {
name: 'Mity',
age: 20,
address: {
city: '東京',
},
};
console.log(user.name); // Mity
console.log(user['age']); // 20
console.log(user.address.city); // 東京
分割代入と値がない場合
const user = {
name: 'Mity',
address: null,
};
const { name } = user;
const city = user.address?.city ?? '未登録';
console.log(name); // Mity
console.log(city); // 未登録
?. は途中の値が null または undefined なら処理を止めて undefined を返します。?? は左側が null または undefined のときだけ右側を使います。
クラスからオブジェクトを作る
同じ構造と処理を持つオブジェクトを複数作る場合は、クラスを設計図として利用できます。constructor は new でオブジェクトを作るときに呼ばれ、this は作成されたオブジェクト自身を表します。
class User {
constructor(name, age) {
this.name = name;
this.age = age;
}
introduce() {
return `${this.name}、${this.age}歳です`;
}
}
const user = new User('Mity', 20);
console.log(user.introduce()); // Mity、20歳です
関数とスコープ
関数は、まとまった処理に名前を付けて再利用する仕組みです。引数で値を受け取り、return で結果を返します。
function calculateTotal(price, count) {
return price * count;
}
const total = calculateTotal(500, 3);
console.log(`合計は${total}円です`);
短い処理ではアロー関数もよく使われます。引数がひとつなら括弧を省略できますが、書き始めは括弧を付けても問題ありません。
const double = (number) => number * 2;
console.log(double(5)); // 10
ブロックスコープ
const と let で宣言した変数は、宣言した波括弧の外からは利用できません。
if (true) {
const message = 'ブロックの中だけで使えます';
console.log(message);
}
// console.log(message); // ReferenceError
this の扱いが異なります。配列の map() などのコールバックには便利ですが、オブジェクトのメソッドで this を使う場合は違いを確認してから選びましょう。HTMLを操作する(DOM)
ブラウザーはHTMLをDOMというオブジェクトの集まりとして扱います。document.querySelector() で要素を探し、プロパティやメソッドを使って内容を変更できます。
<p id="message">変更前の文章</p>
<ul id="fruitList"></ul>
const message = document.querySelector('#message');
const fruitList = document.querySelector('#fruitList');
if (message !== null) {
message.textContent = 'JavaScriptで変更しました';
message.classList.add('is-updated');
}
const fruits = ['りんご', 'みかん', 'ぶどう'];
if (fruitList !== null) {
for (const fruit of fruits) {
const item = document.createElement('li');
item.textContent = fruit;
fruitList.append(item);
}
}
querySelector() は一致する要素がない場合に null を返します。要素が必ず存在すると思い込まず、条件分岐で確認すると「nullのプロパティを読めない」というエラーを防げます。
innerHTML へ代入すると、悪意のあるHTMLやスクリプトが実行される危険があります。単なる文字として表示する場合は textContent を使いましょう。イベントとフォーム
クリック、入力、送信など、ページ上で起きた出来事をイベントと呼びます。addEventListener() を使って、イベントが起きたときの処理を登録します。
クリックで表示を切り替える
<button id="toggleButton" type="button">表示を切り替える</button>
<p id="detail" hidden>詳しい説明です。</p>
const toggleButton = document.querySelector('#toggleButton');
const detail = document.querySelector('#detail');
if (toggleButton !== null && detail !== null) {
toggleButton.addEventListener('click', () => {
detail.hidden = !detail.hidden;
});
}
フォームの入力を確認する
<form id="nameForm">
<label for="nameInput">お名前</label>
<input id="nameInput" name="name" type="text" required>
<button type="submit">送信</button>
</form>
<p id="formMessage" aria-live="polite"></p>
const form = document.querySelector('#nameForm');
const nameInput = document.querySelector('#nameInput');
const formMessage = document.querySelector('#formMessage');
if (form !== null && nameInput !== null && formMessage !== null) {
form.addEventListener('submit', (event) => {
event.preventDefault();
const name = nameInput.value.trim();
if (name === '') {
formMessage.textContent = '名前を入力してください';
return;
}
formMessage.textContent = `こんにちは、${name}さん!`;
});
}
event.preventDefault() は、フォーム送信によるページ移動を止めます。この例はブラウザー内で表示を確認する練習用です。実際にサーバーへデータを保存する場合は、送信先でも入力値を必ず検証してください。
非同期処理とFetch API
通信には時間がかかるため、結果を待つ間もページ全体を止めない非同期処理を使います。async 関数の中では、Promiseの完了を await で待てます。
次の例では、同じサイトに置いたJSONファイルを読み込みます。3つのファイルを同じフォルダー構成で用意してください。
[
{ "id": 1, "name": "Mity" },
{ "id": 2, "name": "Sora" }
]
<button id="loadButton" type="button">利用者を読み込む</button>
<p id="loadStatus" aria-live="polite"></p>
<ul id="userList"></ul>
const loadButton = document.querySelector('#loadButton');
const loadStatus = document.querySelector('#loadStatus');
const userList = document.querySelector('#userList');
async function loadUsers() {
if (loadStatus === null || userList === null) {
return;
}
loadStatus.textContent = '読み込み中です...';
try {
const response = await fetch('./data/users.json');
if (!response.ok) {
throw new Error(`HTTPエラー: ${response.status}`);
}
const users = await response.json();
userList.replaceChildren();
for (const user of users) {
const item = document.createElement('li');
item.textContent = user.name;
userList.append(item);
}
loadStatus.textContent = '読み込みました';
} catch (error) {
console.error(error);
loadStatus.textContent = '読み込みに失敗しました';
}
}
if (loadButton !== null) {
loadButton.addEventListener('click', loadUsers);
}
fetch() は、404や500などのHTTPエラーでは自動的に例外を投げません。response.ok を確認し、失敗なら自分でエラーを投げます。ネットワーク障害やJSONの解析失敗も起こり得るため、try...catch で利用者向けの表示を用意します。
Promiseとthen・catch
async 関数はPromiseを返し、await はその完了を待つ書き方です。同じPromiseは then() で成功後の処理、catch() で失敗時の処理をつなげることもできます。既存のコードやドキュメントでは、次の形式もよく見かけます。
fetch('./data/users.json')
.then((response) => {
if (!response.ok) {
throw new Error(`HTTPエラー: ${response.status}`);
}
return response.json();
})
.then((users) => {
console.log(users);
})
.catch((error) => {
console.error('読み込みに失敗しました', error);
});
async・await と then()・catch() は、どちらもPromiseを扱います。プロジェクトの書き方に合わせ、ひとつの処理の中では読みやすい形式に揃えましょう。file:// でHTMLを直接開くとブラウザーのセキュリティ制限で失敗することがあります。ローカル開発サーバーを使い、http://localhost から確認してください。ブラウザーへデータを保存する
localStorage を使うと、同じオリジンのブラウザーへ文字列を保存できます。ページを閉じても残るため、テーマ設定や入力途中の下書きなどに利用できます。
localStorage.setItem('theme', 'dark');
const theme = localStorage.getItem('theme') ?? 'light';
console.log(theme); // dark
localStorage.removeItem('theme');
localStorageが保存できるのは文字列だけです。配列やオブジェクトは JSON.stringify() で文字列へ変換し、読み込むときに JSON.parse() で元へ戻します。
let savedSettings = null;
try {
const settings = {
theme: 'dark',
fontSize: 16,
};
localStorage.setItem('settings', JSON.stringify(settings));
const savedText = localStorage.getItem('settings');
if (savedText !== null) {
savedSettings = JSON.parse(savedText);
}
} catch (error) {
console.error('設定を保存または読み込みできませんでした', error);
}
console.log(savedSettings);
タブを閉じるまで保存する
sessionStorage は localStorage と同じく文字列を保存しますが、データはブラウザーのタブごとに分かれ、そのタブを閉じると削除されます。同じタブでの再読み込みでは残るため、ページを開いている間だけ保持したい入力内容などに使えます。
sessionStorage.setItem('draft', '入力途中の文章');
const draft = sessionStorage.getItem('draft') ?? '';
console.log(draft); // 入力途中の文章
sessionStorage.removeItem('draft');
file:// で直接開いたページの挙動はブラウザーによって異なるため、ここでもローカル開発サーバーを使いましょう。モジュールとエラーの調べ方
ファイルを役割ごとに分ける
コードが増えたら、関数や値を別ファイルへ分けます。公開する側で export、利用する側で import を使い、HTMLでは type="module" を指定します。
export function add(a, b) {
return a + b;
}
import { add } from './math.js';
console.log(add(2, 3)); // 5
<script type="module" src="main.js"></script>
モジュールの相対パスには ./ と拡張子を含めます。ブラウザーではWebサーバーから正しいJavaScriptのContent-Typeで配信する必要があるため、HTMLを直接開かずローカル開発サーバーを使います。
エラーを段階的に調べる
- Consoleの最初のエラーメッセージと行番号を読む
- 変数が定義され、想定した型と値になっているか確認する
- DOM要素が
nullになっていないか確認する - NetworkパネルでJSやJSONのURLとHTTPステータスを見る
console.log()やブレークポイントで処理の順番を追う
function calculateAverage(scores) {
if (scores.length === 0) {
throw new Error('点数がありません');
}
const total = scores.reduce((sum, score) => sum + score, 0);
return total / scores.length;
}
try {
console.log(calculateAverage([]));
} catch (error) {
console.error(error.message); // 点数がありません
}
この例では空配列を意図的に渡し、エラー処理の動作を確認しています。エラーを無視して処理を続けるのではなく、利用者向けの表示と開発者向けの記録を分けて考えましょう。
練習問題
問題 11から100までの合計を求めよう
for を使って1から100までの数を total へ足し、Consoleへ表示してください。
let total = 0;
for (let i = 1; i <= 100; i += 1) {
total += i;
}
console.log(total); // 5050
問題 2合格者の名前を取り出そう
利用者の配列から60点以上の人を選び、名前だけの新しい配列を作ってください。
const users = [
{ name: 'Mity', score: 82 },
{ name: 'Sora', score: 55 },
{ name: 'Haru', score: 91 },
];
const passedNames = users
.filter((user) => user.score >= 60)
.map((user) => user.name);
console.log(passedNames); // ['Mity', 'Haru']
問題 3カウンターを作ろう
ボタンを押すたびに数字が1増えるページを作ってください。
<button id="countButton" type="button">1増やす</button>
<p>回数: <span id="countValue">0</span></p>
const countButton = document.querySelector('#countButton');
const countValue = document.querySelector('#countValue');
let count = 0;
if (countButton !== null && countValue !== null) {
countButton.addEventListener('click', () => {
count += 1;
countValue.textContent = String(count);
});
}
次に学ぶこと
ここまで理解できたら、短い機能を組み合わせて自分のWebページを作ってみましょう。最初から大きなアプリを目指さず、入力、表示、保存、読み込みを一つずつ追加するのがおすすめです。