組み込み関数

JavaScript Web Storage入門|localStorageでデータを保存する

JavaScript localStorage Web Storage

JavaScript Web Storage入門
localStorageでデータを保存する

localStorageとsessionStorageの使い方を解説。データの保存・取得・削除から、JSON操作、実践的な活用パターンまで学べます。

こんな人向けの記事です

  • ブラウザにデータを保存したい
  • localStorageとsessionStorageの違いを知りたい
  • ダークモードやフォーム下書き保存を実装したい

Step 1Web Storageとは

Web Storageは、ブラウザにキーと値のペアでデータを保存するためのAPIです。Cookieに代わるモダンなクライアントサイドストレージとして、localStoragesessionStorageの2種類が用意されています。

localStorage vs sessionStorage vs Cookie

特性localStoragesessionStorageCookie
有効期限明示的に削除するまで永続タブ/ウィンドウを閉じるまで設定した有効期限まで
容量約5〜10MB約5〜10MB約4KB
サーバー送信されないされない毎リクエスト自動送信
アクセス範囲同一オリジンの全タブ同一タブのみ同一オリジン(パス制限可)
APIシンプル(getItem/setItem)シンプル(getItem/setItem)文字列操作が必要
JavaScript
// 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 — データの保存

JavaScript
// キーと値のペアで保存(値は文字列に変換される)
localStorage.setItem('username', '山田太郎');
localStorage.setItem('visitCount', '5');
localStorage.setItem('isLoggedIn', 'true');

// 既存のキーに保存すると上書きされる
localStorage.setItem('username', '佐藤花子');

getItem — データの取得

JavaScript
// キーを指定して値を取得
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 — データの削除

JavaScript
// 指定したキーのデータを削除
localStorage.removeItem('visitCount');

// 存在しないキーを指定してもエラーにはならない
localStorage.removeItem('nonExistent');

clear — 全データの削除

JavaScript
// 同一オリジンの localStorage を全てクリア
localStorage.clear();

// length プロパティでデータ数を確認
console.log(localStorage.length); // 0

その他の操作

JavaScript
// 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()で復元します。

オブジェクトの保存と取得

JavaScript
// オブジェクトの保存
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"

配列の保存と取得

JavaScript
// 配列の保存
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));

安全な読み込みヘルパー

JavaScript
// 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 できないもの

以下のデータ型はJSON.stringify()で正しく変換できません。

  • 関数 — 無視される(undefined 扱い)
  • undefined — プロパティごと無視される
  • Symbol — 無視される
  • Date — 文字列に変換される(new Date() で復元が必要)
  • Map / Set — 空オブジェクト {} になる
  • 循環参照TypeError が発生する

Step 4ストレージイベントの活用

storageイベントを使うと、他のタブやウィンドウで行われたlocalStorageの変更をリアルタイムに検知できます。同一オリジンの複数タブでデータを同期したいときに便利です。

JavaScript
// 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イベントは変更を行ったタブ自身では発火しません。あくまで「他のタブ」での変更を検知するためのイベントです。同じタブ内で変更を検知したい場合は、自前で通知する仕組みが必要です。

実用例: タブ間でログイン状態を同期

JavaScript
// タブ間でログアウトを同期する
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 のプロパティ一覧

プロパティ説明
keystring | null変更されたキー(clear()の場合はnull
oldValuestring | null変更前の値(新規追加の場合はnull
newValuestring | null変更後の値(削除の場合はnull
urlstring変更が発生したページのURL
storageAreaStorage変更された 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万文字が保存可能です。キー名のサイズも容量に含まれます。

安全な保存関数

JavaScript
// 容量超過を考慮した安全な保存関数
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 は利用できません');
}

使用量を確認する

JavaScript
// 現在の 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 + JavaScript
<!-- 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>
FOUC(ちらつき)を防ぐ

テーマの復元は<head>内のインラインスクリプトで行うと、ページ描画前にテーマが適用されて画面のちらつきを防げます。

フォーム下書き保存

HTML + JavaScript
<!-- 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のハンドリングが必要
  • 機密情報の保存には使わず、HttpOnly Cookie を使うこと