localStorageは同一オリジンへ文字列を保存するが正本データには使わない
公開日: 執筆:佐藤
localStorageは、画面の表示方法や入力途中の補助情報など、消えても事業記録を失わない値をブラウザーへ保存するときに使います。値は同一オリジンへ保存され、ブラウザーを閉じたあとも残せますが、利用者による削除、保存拒否、容量超過があるため、注文、権限、在庫などの正本にはしません。
基本操作は、setItem()で保存し、getItem()で取得し、不要になったキーをremoveItem()で削除することです。保存値は文字列になるため、数値や構造化した値は、保存時と取得時の変換方法まで決めます。
setItemとgetItemで一つのキーを扱う
W3CのWeb Storage Recommendationは、Storageをキーと値の組として定義しています。表示モードを保存する最小例は次のようになります。
var storageKey = 'display.mode';
var displayMode = 'standard';
try {
localStorage.setItem(storageKey, 'compact');
var savedValue = localStorage.getItem(storageKey);
if (savedValue !== null) {
displayMode = savedValue;
}
} catch (error) {
displayMode = 'standard';
}
getItem()は、キーがなければnullを返します。空文字列を保存した場合は''が返るため、存在確認をif (savedValue)だけで済ませると、空文字列と未保存を区別できません。
不要になった一項目は、キーを指定して削除します。
localStorage.removeItem('display.mode');
clear()は同じStorage内の全項目を削除します。自分の機能が作ったキーだけを消す場面で使うと、同じオリジンにある別機能の設定まで失わせます。通常はremoveItem()を選びます。
保存される値は文字列として読み戻す
StorageのsetItem()は、キーと値をDOMStringとして受け取ります。数値を渡しても、取得時は文字列です。
localStorage.setItem('list.pageSize', 20);
var pageSize = Number(localStorage.getItem('list.pageSize'));
if (!isFinite(pageSize) || pageSize < 1) {
pageSize = 20;
}
複数の値を一つにまとめる場合は、JSONへ変換して保存できます。
var viewSettings = {
mode: 'compact',
pageSize: 20
};
localStorage.setItem('list.settings', JSON.stringify(viewSettings));
取得時はJSON.parse()を使いますが、保存済みの文字列が壊れている場合や、古い形式が残っている場合に失敗します。tryとcatchで既定値へ戻し、取得したプロパティの型と範囲も確認します。
ブラウザーへ保存した値は、利用者が編集できない設定ではありません。JavaScriptから書き換えられる入力値として扱い、権限判定や料金計算をそのまま信用しません。
同一オリジンは同じパスという意味ではない
localStorageの保存領域は、オリジンごとに分かれます。オリジンは、URLのscheme、host、portの組です。
| URLの組み合わせ | 保存領域 | 理由 |
|---|---|---|
https://example.com/aとhttps://example.com/b |
同じ | パスはオリジンを分けない |
http://example.comとhttps://example.com |
別 | schemeが違う |
https://example.comとhttps://www.example.com |
別 | hostが違う |
https://example.comとhttps://example.com:8443 |
別 | portが違う |
同じhostを複数の制作者やシステムが共有しても、パスごとに保存領域を隔離する仕組みはありません。/shop/と/members/が同じオリジンなら、両方のスクリプトが同じlocalStorageへアクセスできます。
キーには、shop.display.modeのように機能名を含めます。ただし、名前を分けてもアクセス権を分離したことにはなりません。互いに読めない状態が必要なら、オリジンまたはサーバー側の権限を分けます。
保存できない場合を通常の分岐として扱う
localStorageが存在しても、保存が必ず成功するとは限りません。利用者が保存を許可していない場合や、利用できる容量を超えた場合、setItem()はQuotaExceededErrorを投げます。ポリシーによってlocalStorageへのアクセス自体が拒否される場合もあります。
したがって、機能の開始時に一度だけ存在を確認するのではなく、保存処理をtryとcatchで扱います。保存に失敗した場合は、次のどちらかへ戻します。
- 表示設定なら、画面を標準値で表示する
- 入力補助なら、保存できなかったことを利用者へ知らせ、送信処理は続けられるようにする
保存できないだけで、問い合わせや購入を完了できなくなる設計は避けます。補助機能の失敗を事業処理の失敗へ広げないためです。
永続保存と正本データを分ける
W3C仕様は、利用者の要求やセキュリティー上の理由によって保存データが削除される場合を認めています。利用者はブラウザーの操作でも削除できます。別の端末や別のブラウザーへ自動で共有される保存領域でもありません。
| localStorageに置ける候補 | サーバーを正本にする値 |
|---|---|
| 表示モード | 注文と決済の状態 |
| 一覧の並び順 | 在庫数と価格 |
| 閉じた案内の状態 | 利用者の権限 |
| 送信前の入力補助 | 受け付けた問い合わせ |
正本データは、サーバーで検証し、利用者、更新日時、処理結果とともに保存します。localStorageには、正本を再取得するための表示条件や、消えても再設定できる値だけを置きます。
個人情報や秘密情報も、同じオリジンで動くスクリプトから読めることを前提にします。保存期間が長いほど安全になるわけではありません。目的が終わったキーはremoveItem()で消します。
複数画面の同期はstorageイベントで補助する
同じオリジンを別のWindowで開いている場合、setItem()、removeItem()、clear()による変更は、影響を受けるほかのDocumentへstorageイベントとして通知されます。変更を行ったStorage自身へ同じイベントが戻る仕組みではありません。
表示モードを複数画面でそろえる用途には使えますが、サーバー上の注文処理や排他制御の代わりにはなりません。イベントを受け取った側でも値を検査し、画面の再表示にだけ使います。
実装前に、キー名、文字列への変換、未保存時の既定値、容量超過時の動作、削除時期、サーバー側の正本を一表にしてください。保存APIを呼べることより、消えた場合にも業務が止まらないことを優先します。
関連記事

