JavaScript Web Storage入門
localStorageでデータを保存する
localStorageとsessionStorageの使い方を解説。データの保存・取得・削除から、JSON操作、実践的な活用パターンまで学べます。
こんな人向けの記事です
- ブラウザにデータを保存したい
- localStorageとsessionStorageの違いを知りたい
- ダークモードやフォーム下書き保存を実装したい
Step 1Web Storageとは
Web Storageは、ブラウザにキーと値のペアでデータを保存するためのAPIです。Cookieに代わるモダンなクライアントサイドストレージとして、localStorageとsessionStorageの2種類が用意されています。
localStorage vs sessionStorage vs Cookie
| 特性 | localStorage | sessionStorage | Cookie |
|---|---|---|---|
| 有効期限 | 明示的に削除するまで永続 | タブ/ウィンドウを閉じるまで | 設定した有効期限まで |
| 容量 | 約5〜10MB | 約5〜10MB | 約4KB |
| サーバー送信 | されない | されない | 毎リクエスト自動送信 |
| アクセス範囲 | 同一オリジンの全タブ | 同一タブのみ | 同一オリジン(パス制限可) |
| API | シンプル(getItem/setItem) | シンプル(getItem/setItem) | 文字列操作が必要 |
// localStorage — ブラウザを閉じてもデータが残る
localStorage.setItem('theme', 'dark');
// sessionStorage — タブを閉じるとデータが消える
sessionStorage.setItem('formStep', '2');
// Cookie — サーバーに毎回送信される(Web Storage より不便)
document.cookie = 'theme=dark; max-age=86400; path=/';
localStorage:ユーザー設定やテーマなど、長期保存したいデータ
sessionStorage:フォームの途中状態やワンタイムのフラグなど、一時的なデータ
Cookie:認証トークンなど、サーバーに送信する必要があるデータ
Step 2localStorageの基本操作
localStorageには4つの基本メソッドがあります。すべて同期的に動作し、値は常に文字列として保存されます。
setItem — データの保存
// キーと値のペアで保存(値は文字列に変換される)
localStorage.setItem('username', '山田太郎');
localStorage.setItem('visitCount', '5');
localStorage.setItem('isLoggedIn', 'true');
// 既存のキーに保存すると上書きされる
localStorage.setItem('username', '佐藤花子');
getItem — データの取得
// キーを指定して値を取得
const username = localStorage.getItem('username');
console.log(username); // "佐藤花子"
// 存在しないキーは null が返る
const email = localStorage.getItem('email');
console.log(email); // null
// null チェックでデフォルト値を設定
const theme = localStorage.getItem('theme') || 'light';
console.log(theme); // "light"
removeItem — データの削除
// 指定したキーのデータを削除
localStorage.removeItem('visitCount');
// 存在しないキーを指定してもエラーにはならない
localStorage.removeItem('nonExistent');
clear — 全データの削除
// 同一オリジンの localStorage を全てクリア
localStorage.clear();
// length プロパティでデータ数を確認
console.log(localStorage.length); // 0
その他の操作
// key() — インデックスからキー名を取得
localStorage.setItem('a', '1');
localStorage.setItem('b', '2');
console.log(localStorage.key(0)); // "a"(順序は保証されない)
// length — 保存件数を取得
console.log(localStorage.length); // 2
// 全件ループ
for (let i = 0; i < localStorage.length; i++) {
const key = localStorage.key(i);
console.log(key, localStorage.getItem(key));
}
// Object.keys を使ったループ
Object.keys(localStorage).forEach(key => {
console.log(key, localStorage.getItem(key));
});
localStorageの値は常に文字列です。数値や真偽値を保存しても文字列として取得されます。
localStorage.setItem('count', 5) → localStorage.getItem('count') は "5"(文字列)です。
Step 3JSONデータの保存と読み込み
localStorageは文字列しか保存できないため、オブジェクトや配列はJSON.stringify()で文字列に変換してから保存し、JSON.parse()で復元します。
オブジェクトの保存と取得
// オブジェクトの保存
const user = {
id: 1,
name: '山田太郎',
email: 'yamada@example.com',
preferences: { theme: 'dark', fontSize: 16 }
};
localStorage.setItem('user', JSON.stringify(user));
// オブジェクトの取得
const savedUser = JSON.parse(localStorage.getItem('user'));
console.log(savedUser.name); // "山田太郎"
console.log(savedUser.preferences.theme); // "dark"
配列の保存と取得
// 配列の保存
const todos = [
{ id: 1, text: '買い物', done: false },
{ id: 2, text: '掃除', done: true },
{ id: 3, text: '料理', done: false }
];
localStorage.setItem('todos', JSON.stringify(todos));
// 配列の取得(存在しない場合は空配列をデフォルトに)
const savedTodos = JSON.parse(localStorage.getItem('todos')) || [];
console.log(savedTodos.length); // 3
// 配列にアイテムを追加して再保存
savedTodos.push({ id: 4, text: '洗濯', done: false });
localStorage.setItem('todos', JSON.stringify(savedTodos));
安全な読み込みヘルパー
// JSON.parse が壊れたデータで例外を投げる対策
function safeGetJSON(key, defaultValue = null) {
try {
const raw = localStorage.getItem(key);
return raw !== null ? JSON.parse(raw) : defaultValue;
} catch (e) {
console.warn('JSON parse error for key:', key, e);
return defaultValue;
}
}
// 使用例
const settings = safeGetJSON('settings', { theme: 'light' });
console.log(settings.theme); // "light"(データが無い場合)
以下のデータ型はJSON.stringify()で正しく変換できません。
- 関数 — 無視される(
undefined扱い) - undefined — プロパティごと無視される
- Symbol — 無視される
- Date — 文字列に変換される(
new Date()で復元が必要) - Map / Set — 空オブジェクト
{}になる - 循環参照 —
TypeErrorが発生する
Step 4ストレージイベントの活用
storageイベントを使うと、他のタブやウィンドウで行われたlocalStorageの変更をリアルタイムに検知できます。同一オリジンの複数タブでデータを同期したいときに便利です。
// storage イベントは「別のタブ」で変更があったときに発火する
window.addEventListener('storage', (event) => {
console.log('変更されたキー:', event.key);
console.log('変更前の値:', event.oldValue);
console.log('変更後の値:', event.newValue);
console.log('変更されたURL:', event.url);
// event.storageArea で localStorage か sessionStorage かを判別
if (event.storageArea === localStorage) {
console.log('localStorage が変更されました');
}
});
storageイベントは変更を行ったタブ自身では発火しません。あくまで「他のタブ」での変更を検知するためのイベントです。同じタブ内で変更を検知したい場合は、自前で通知する仕組みが必要です。
実用例: タブ間でログイン状態を同期
// タブ間でログアウトを同期する
window.addEventListener('storage', (event) => {
if (event.key === 'isLoggedIn' && event.newValue === null) {
// 別タブでログアウトされた
alert('別のタブでログアウトしました。ページをリロードします。');
window.location.reload();
}
if (event.key === 'theme') {
// 別タブでテーマが変更された → 即座に反映
document.documentElement.setAttribute('data-theme', event.newValue);
}
});
// ログアウト処理
function logout() {
localStorage.removeItem('isLoggedIn');
localStorage.removeItem('authToken');
window.location.href = '/login';
}
StorageEvent のプロパティ一覧
| プロパティ | 型 | 説明 |
|---|---|---|
key | string | null | 変更されたキー(clear()の場合はnull) |
oldValue | string | null | 変更前の値(新規追加の場合はnull) |
newValue | string | null | 変更後の値(削除の場合はnull) |
url | string | 変更が発生したページのURL |
storageArea | Storage | 変更された Storage オブジェクト |
Step 5容量制限とエラーハンドリング
localStorageにはブラウザごとに容量制限があります。制限を超えるとQuotaExceededErrorが発生するため、適切なエラーハンドリングが必要です。
ブラウザごとの容量目安
| ブラウザ | localStorage 上限 | sessionStorage 上限 |
|---|---|---|
| Chrome | 約5MB | 約5MB |
| Firefox | 約10MB | 約10MB |
| Safari | 約5MB | 約5MB |
| Edge | 約5MB | 約5MB |
JavaScriptの文字列はUTF-16で格納されるため、1文字 = 2バイトで計算されます。5MBの場合、約250万文字が保存可能です。キー名のサイズも容量に含まれます。
安全な保存関数
// 容量超過を考慮した安全な保存関数
function safeSave(key, value) {
try {
const data = typeof value === 'object'
? JSON.stringify(value)
: String(value);
localStorage.setItem(key, data);
return true;
} catch (e) {
if (e.name === 'QuotaExceededError' ||
e.code === 22 || e.code === 1014) {
console.error('ストレージの容量が不足しています');
// 古いデータを削除するなどの対策
return false;
}
throw e; // その他のエラーは再スロー
}
}
// Web Storage が使えるか判定する
function isStorageAvailable(type) {
try {
const storage = window[type];
const testKey = '__storage_test__';
storage.setItem(testKey, 'test');
storage.removeItem(testKey);
return true;
} catch (e) {
return false;
}
}
// 使用例
if (isStorageAvailable('localStorage')) {
safeSave('data', { key: 'value' });
} else {
console.warn('localStorage は利用できません');
}
使用量を確認する
// 現在の localStorage 使用量を計算
function getStorageUsage() {
let total = 0;
for (let i = 0; i < localStorage.length; i++) {
const key = localStorage.key(i);
const value = localStorage.getItem(key);
// UTF-16 なので 1文字 = 2バイト
total += (key.length + value.length) * 2;
}
return {
bytes: total,
kb: (total / 1024).toFixed(2),
mb: (total / (1024 * 1024)).toFixed(4)
};
}
const usage = getStorageUsage();
console.log(usage.kb + ' KB 使用中'); // 例: "12.34 KB 使用中"
Safari のプライベートブラウジングでは、localStorageに書き込むとQuotaExceededErrorが発生します(容量0扱い)。isStorageAvailable()のようなチェック関数で事前に判定しましょう。
Step 6実践例(ダークモード・フォーム下書き保存)
ここまでの知識を使って、実際のWebサイトで役立つ2つの実装パターンを紹介します。
ダークモードの保存と復元
<!-- HTML -->
<button id="theme-toggle">テーマ切替</button>
<script>
const toggle = document.getElementById('theme-toggle');
const THEME_KEY = 'site-theme';
// ページ読み込み時にテーマを復元
function loadTheme() {
const saved = localStorage.getItem(THEME_KEY);
// 保存値があればそれを、なければ OS 設定を参照
if (saved) {
document.documentElement.setAttribute('data-theme', saved);
} else if (window.matchMedia('(prefers-color-scheme: dark)').matches) {
document.documentElement.setAttribute('data-theme', 'dark');
}
}
// テーマを切り替えて保存
toggle.addEventListener('click', () => {
const current = document.documentElement.getAttribute('data-theme');
const next = current === 'dark' ? 'light' : 'dark';
document.documentElement.setAttribute('data-theme', next);
localStorage.setItem(THEME_KEY, next);
});
// 別タブでの切替も同期
window.addEventListener('storage', (e) => {
if (e.key === THEME_KEY) {
document.documentElement.setAttribute('data-theme', e.newValue);
}
});
// 初期化
loadTheme();
</script>
テーマの復元は<head>内のインラインスクリプトで行うと、ページ描画前にテーマが適用されて画面のちらつきを防げます。
フォーム下書き保存
<!-- HTML -->
<form id="contact-form">
<input type="text" name="name" placeholder="名前">
<input type="email" name="email" placeholder="メール">
<textarea name="message" placeholder="メッセージ"></textarea>
<button type="submit">送信</button>
<button type="button" id="clear-draft">下書き削除</button>
</form>
<script>
const form = document.getElementById('contact-form');
const DRAFT_KEY = 'contact-draft';
// 下書きを復元
function loadDraft() {
const draft = safeGetJSON(DRAFT_KEY);
if (!draft) return;
Object.entries(draft).forEach(([name, value]) => {
const field = form.elements[name];
if (field) field.value = value;
});
}
// フォームの内容を保存(入力のたびに呼ばれる)
function saveDraft() {
const data = {};
new FormData(form).forEach((value, key) => {
data[key] = value;
});
localStorage.setItem(DRAFT_KEY, JSON.stringify(data));
}
// JSON.parse の安全なヘルパー(Step 3 で紹介)
function safeGetJSON(key, defaultValue = null) {
try {
const raw = localStorage.getItem(key);
return raw !== null ? JSON.parse(raw) : defaultValue;
} catch (e) {
return defaultValue;
}
}
// イベント登録
form.addEventListener('input', saveDraft);
form.addEventListener('submit', () => {
localStorage.removeItem(DRAFT_KEY); // 送信成功で下書き削除
});
document.getElementById('clear-draft').addEventListener('click', () => {
localStorage.removeItem(DRAFT_KEY);
form.reset();
});
// 初期化
loadDraft();
</script>
localStorageにパスワードやトークンなどの機密情報を保存してはいけません。XSS攻撃でJavaScriptが実行されると、localStorageの全データが盗まれます。認証情報はHttpOnly属性付きのCookieで管理しましょう。
まとめ
この記事で学んだこと
localStorageは永続保存、sessionStorageはタブ単位の一時保存setItem/getItem/removeItem/clearの4メソッドが基本- オブジェクトや配列は
JSON.stringify()/JSON.parse()で変換する storageイベントで他タブの変更をリアルタイムに検知できる- 容量制限(約5MB)があるため、
QuotaExceededErrorのハンドリングが必要 - 機密情報の保存には使わず、
HttpOnlyCookie を使うこと