JavaScriptの基礎 - Web Storage
概要
Web Storage APIは、Webブラウザがキー・バリューペア形式のデータをクライアント側に保存するための仕組みである。
WHATWG HTML Web Storage仕様に基づいて実装されており、全ての主要なWebブラウザでサポートされている。
Web Storageには、localStorage と sessionStorage の2つのオブジェクトが存在する。
localStorage はWebブラウザを閉じても永続的にデータを保存し、sessionStorage はタブを閉じるとデータが削除される。
Web Storage APIが保存できるデータは文字列のみである。
オブジェクトや配列を保存する場合は、JSON.stringify() で文字列に変換してから保存し、取得時に JSON.parse() で復元する必要がある。
他のストレージ手段との位置付けとして、Cookieはサーバとの通信に付随する小容量のストレージ (最大4[KB]) であり、IndexedDBは大容量・複雑なデータを扱うための非同期APIである。
Web Storageはこれら2つの中間に位置する手段であり、シンプルなキー・バリュー形式のデータを5〜10[MB]程度まで保存する用途に適している。
Web Storageの基本
localStorage と sessionStorageの違い
下表に、localStorage と sessionStorage の主な違いを示す。
| 項目 | localStorage | sessionStorage |
|---|---|---|
| ライフサイクル | 明示的に削除するまで永続 | タブ・ウィンドウを閉じると削除 |
| スコープ | 同じオリジンの全てのタブ・ウィンドウ | 各タブ・ウィンドウで独立 |
| 容量 | 約5〜10[MB] | 約5〜10[MB] |
| ページリロード | 保持される | 保持される |
| 主な用途 | ユーザ設定、テーマ設定、ログイン状態 | フォーム入力の一時保存、ページ間の状態管理 |
sessionStorage は、タブを閉じると削除される点を除いてほぼ同じAPIを持つ。
ページのリロードや、Webブラウザの[戻る] / [進む]操作の後もデータは保持される。
データの保存と取得
Web Storage APIのメソッドは、localStorage と sessionStorage のどちらでも共通して使用できる。
以下の説明では localStorage を例に示す。
setItem / getItem
setItem(key, value) は、指定したキーとバリューのペアを保存する。
キーが既に存在する場合はバリューが上書きされる。
キーとバリューはどちらも文字列として保存される。
getItem(key) は、指定したキーに対応するバリューを返す。
キーが存在しない場合は null を返す。
// データの保存
localStorage.setItem("username", "Alice");
localStorage.setItem("theme", "dark");
// データの取得
const username = localStorage.getItem("username");
console.log(username); // "Alice"
// 存在しないキーの取得
const missing = localStorage.getItem("nonExistentKey");
console.log(missing); // null
ストレージの容量を超えた場合、setItem() は 例外 QuotaExceededError (DOMException) を発生させる。
エラーハンドリングを行う場合は、try...catch を使用する。
function saveWithQuotaCheck(key, value) {
try {
localStorage.setItem(key, value);
}
catch (e) {
if (e.name === "QuotaExceededError") {
console.error("ストレージ容量が超過しました");
}
}
}
removeItem / clear
removeItem(key) は、指定したキーとそのバリューをストレージから削除する。
指定したキーが存在しない場合は何も起こらない。
clear() は、ストレージ内の全てのキーを削除する。
// 特定のキーを削除
localStorage.removeItem("theme");
// 全データを削除
localStorage.clear();
key / length
length は読み取り専用プロパティで、ストレージに保存されているデータ項目の数を返す。
key(index) は、指定したインデックス (0始まり) に対応するキー名を返す。
インデックスが範囲外の場合は、null を返す。
key() と length を組み合わせることにより、ストレージ内の全データをイテレーションできる。
// ストレージ内の全てのキーをイテレーション
for (let i = 0; i < localStorage.length; i++) {
const key = localStorage.key(i);
const value = localStorage.getItem(key);
console.log(`${key}: ${value}`);
}
JSON変換
文字列のみ保存可能な理由
Web Storage APIの仕様上、キーとバリューはどちらも文字列型として保存される。
数値、真偽値、オブジェクト、配列等を直接保存しようとすると、自動的に文字列へ変換される。
例えば、数値 42 を保存すると文字列 "42" として保存されるため、取得後に数値として扱う場合は型変換が必要になる。
オブジェクトを直接保存すると "[object Object]" という文字列に変換され、元のデータを復元できなくなる。
このため、文字列以外のデータを保存する場合は JSON.stringify() と JSON.parse() を使用する必要がある。
オブジェクトと配列の保存
オブジェクトや配列を保存する場合は、JSON.stringify() でJSON文字列に変換してから保存する。
取得時は JSON.parse() で元のデータ型に復元する。
// オブジェクトの保存
const userSettings = {
theme: "dark",
fontSize: 14,
language: "ja",
};
localStorage.setItem("settings", JSON.stringify(userSettings));
// オブジェクトの取得
const savedSettings = JSON.parse(localStorage.getItem("settings"));
console.log(savedSettings.theme); // "dark"
// 配列の保存
const bookmarks = ["page1", "page2", "page3"];
localStorage.setItem("bookmarks", JSON.stringify(bookmarks));
// 配列の取得
const savedBookmarks = JSON.parse(localStorage.getItem("bookmarks"));
console.log(savedBookmarks[0]); // "page1"
型情報の注意点
JSON.stringify() による変換では、一部のデータ型に関する情報が失われる点に注意が必要である。
下表に示す型は JSON変換で失われる または データが変化する。
| 型 | 説明 |
|---|---|
| undefined (オブジェクトのプロパティ値) | オブジェクトのプロパティ値が undefined の場合、そのプロパティはJSONに含まれない。 |
| 関数 (Function) | 関数はJSONに変換されず、プロパティごと除外される。 |
| シンボル (Symbol) | シンボルはJSONに変換されず、プロパティごと除外される。 |
Date オブジェクト |
ISO 8601形式の文字列に変換される。JSON.parse() 後は Date オブジェクトではなく文字列のままになる。
|
| undefined (配列の要素) | 配列内の undefined 要素は null に変換される。 |
Date オブジェクトを復元する場合は、JSON.parse() の第2引数にリバイバー関数を使用することで対応できる。
// Dateオブジェクトを含むデータの保存
const data = {
name: "イベント",
createdAt: new Date(),
};
localStorage.setItem("event", JSON.stringify(data));
// リバイバー関数でDateを復元
function dateReviver(key, value) {
if (typeof value === "string") {
const datePattern = /^\d{4}-\d{2}-\d{2}T/;
if (datePattern.test(value)) {
return new Date(value);
}
}
return value;
}
const savedData = JSON.parse(localStorage.getItem("event"), dateReviver);
console.log(savedData.createdAt instanceof Date); // true
storageイベント
他タブとの同期
storage イベントは、同じオリジンを共有する別のタブ・ウィンドウで localStorage に変更が加えられた場合に発生する。
変更を行ったタブ・ウィンドウ自身ではイベントは発生しないことに注意が必要である。
sessionStorage の変更は他のタブと共有されないため、storage イベントは発生しない。
下表に、StorageEvent オブジェクトのプロパティを示す。
| プロパティ | 説明 |
|---|---|
key |
変更されたキー名clear() が呼び出された場合は null になる。
|
oldValue |
変更前の値 新規追加の場合は null になる。 |
newValue |
変更後の値 削除の場合は null になる。 |
url |
ストレージ変更を引き起こしたドキュメントのURL |
storageArea |
変更が加えられた Storage オブジェクト(localStorage または sessionStorage) |
// 他のタブからのlocalStorage変更を検知する
window.addEventListener("storage", (event) => {
console.log("変更されたキー:", event.key);
console.log("前の値:", event.oldValue);
console.log("新しい値:", event.newValue);
console.log("変更元URL:", event.url);
if (event.key === "theme") {
// テーマが変更されたときの処理
applyTheme(event.newValue);
}
});
この仕組みを利用することにより、複数タブ間でユーザ設定やログイン状態等を同期させることができる。
他のストレージ手段との比較
Cookie / IndexedDB / Web Storage の比較テーブル
主なクライアント側のストレージ手段の比較を以下に示す。
| 項目 | Web Storage (localStorage / sessionStorage) | Cookie | IndexedDB |
|---|---|---|---|
| 容量 | 5〜10[MB] | 4[KB] | 10[GB]以上 |
| データ型 | 文字列のみ | 文字列のみ | オブジェクト・Blob等多様 |
| API型 | 同期 | 同期 | 非同期 |
| サーバへの自動送信 | しない | 全てのHTTPリクエストで自動送信 | しない |
| インデックス・クエリ | 不可 | 不可 | 可能 |
| JavaScript以外のアクセス | 不可 | サーバ側からもアクセス可能 | 不可 |
| 有効期限設定 | 手動削除が必要 | Expires / Max-Age属性で設定可能 | 手動削除が必要 |
| HttpOnlyによるJSアクセス防止 | 不可 | 可能 | 不可 |
| 学習難度 | 低い | 低〜中 | 高い |
| 主な用途 | 設定・状態の簡易保存 | 認証・セッション管理 | 大容量データ・複雑なクエリ |
Tauriアプリでの永続化手段
Tauriアプリケーションでは、WebViewの内部で動作するWeb Storageの他に、ネイティブ側のファイルシステムやデータベースを利用した永続化手段が提供されている。
| 項目 | Web Storage | tauri-plugin-store | tauri-plugin-sql |
|---|---|---|---|
| ストレージ方式 | WebView内部 (文字列) | ファイルシステム (JSON) | データベース (SQL) |
| 容量制限 | 3〜5[MB] | 制限なし | 制限なし |
| API型 | 同期 | 非同期 | 非同期 |
| Rustからのアクセス | 不可 | 可能 | 可能 |
| データ型 | 文字列のみ | 多様 (JSON対応) | 多様 (SQL型) |
| 主な用途 | 一時的なUI状態 | アプリ設定・ユーザデータ | リレーショナルデータ |
tauri-plugin-store は、アプリ設定やユーザデータをファイルシステム上のJSONファイルに永続化するためのプラグインである。
Web Storageと比較して容量制限がなく、Rust側からもアクセスできる点が特徴である。
import { Store } from "@tauri-apps/plugin-store";
const store = new Store(".settings.json");
// データの保存
await store.set("apiKey", "secret-key-value");
// データの取得
const apiKey = await store.get("apiKey");
console.log(apiKey); // "secret-key-value"
アプリ設定や機密性のないユーザデータの永続化には、tauri-plugin-store が適している。
Web Storageは一時的なUI状態の保持やRust側からのアクセスが不要な軽量なデータの保存に向いている。
セキュリティ上の注意事項
localStorage と sessionStorage はJavaScriptから完全にアクセス可能であるため、
XSS (クロスサイトスクリプティング) 攻撃によって悪意あるスクリプトが格納データを読み取る可能性がある。
HTTP-only Cookieとは異なり、Web StorageにはJavaScriptベースの攻撃に対する組み込み保護機能がない。
以下の情報はWeb Storageに格納してはならない。
- 認証トークン (JWT、セッションID)
- APIキーやシークレット
- パスワード
- 個人識別情報 (PII)
- 支払い情報
認証トークンはWeb Storageではなく、HTTP-only Cookieで管理することを推奨する。
セキュリティを向上させるために推奨される対策を以下に示す。
- HTTP-only Cookieの使用
- セッション認証トークンの保管にはHTTP-only Cookieを使用する。
- JavaScriptからアクセスできないためXSSによる漏洩を防げる。
- CSP (コンテンツセキュリティポリシー) の設定
- 厳密なCSPを設定することで、不正なスクリプトの実行を防止する。
- ログアウト時のストレージクリア
- ユーザがログアウトした時に
localStorage.clear()を呼び出し、残存データを削除する。
- ユーザがログアウトした時に
- HTTPSの使用
- 通信の暗号化により、中間者攻撃によるデータ盗取を防止する。
- サーバ側での入力検証・出力エスケープ
- XSSの根本的な原因となるスクリプトインジェクションをサーバ側で防止する。
関連情報
- WHATWG HTML Living Standard - Web Storage
- MDN Web Docs - Web Storage API
- MDN Web Docs - Window.localStorage
- MDN Web Docs - Window.sessionStorage
- MDN Web Docs - StorageEvent
- Tauri - tauri-plugin-store