[JS]Web Storageの仕様と実装 localStorage, sessionStorage
クライアントサイドJavaScriptには、Web Storage(ローカルストレージ/セッションストレージ)という、ブラウザにデータを保存する仕組みがあります。Web Storageの仕様と実装について、解説します(サンプルあり)。
Web Storage:ブラウザにデータを保存
Web Storageは、ブラウザにキーバリュー形式でデータを保存するものです。
Cookieのようにサーバに送信されることはなく、オリジン(スキーマ://ホスト名:ポート番号)ごとに、5~10MB程度のデータを保存することができます。
同一オリジンのページのみ、Web Storageに保存されたデータにアクセスすることができます。
また、Web Storageのデータは、開発者ツール(F12)の「アプリケーション」タブで、確認することができます。データの追加、編集、削除も可能です。
ストレージに保存できるデータは、文字列のみです。文字列以外を保存しようとすると、自動的に文字列化して保存されます。
そのため、オブジェクトを保存する場合は、JSONに変換してから保存するようにします。
Web Storageには、ローカルストレージ(local storage)とセッションストレージ(session storage)があります。
両者は、データの有効期間とスコープが異なります。
ローカルストレージは、データの有効期間は永続です。明示的に削除しない限り、ブラウザを閉じても、ページ遷移しても削除されません。ページを再開すれば、保存したデータを利用することができます。
データのスコープは、ドキュメントのオリジン単位です。同一オリジンのドキュメントを、複数のウィンドウで開いている場合、データを共有することができます。
セッションストレージは、データの有効期間はブラウザセッションとなります。ブラウザ(タブ)を閉じると削除されます。
データのスコープは、ドキュメントのオリジン単位かつ、ウィンドウ単位です。同一オリジンのドキュメントを開いている場合でも、別のウィンドウとデータを共有することはできません。
ストレージ | 有効期間 | スコープ |
---|---|---|
ローカルストレージ | 永続 | オリジン単位 |
セッションストレージ | ブラウザセッション | オリジン単位、ウィンドウ単位 |
上記の違い以外は、ローカルストレージもセッションストレージも、操作方法は全く同じです。
Web Storageは、WindowオブジェクトのlocalStorage/sessionStorageプロパティ(Storageオブジェクトを参照)でアクセスします。
Storageオブジェクトには、getItem/setItemメソッドがありますが、プロパティ構文やブラケット構文で直接データにアクセスすることができます。
データの削除は、removeItemメソッドや、delete演算子を使用します。あるオリジンの全てのデータを削除する場合は、clearメソッドを使用します。
HTML
<form>
<label for="job">Job:</label><br>
<input type="text" name="job" id="job" value="Engineer"><br>
<label for="position">Position:</label><br>
<input type="text" name="position" id="position" value="SE"><br>
<label for="skill">Skill:</label><br>
<input type="text" name="skill" id="skill" value="JavaScript">
<input type="button" id="save" value="Save">
<input type="button" id="view" value="View">
</form>
<hr>
<table id="table"></table>
<script src="storage.js"></script>
JavaScript
document.addEventListener(
'DOMContentLoaded',
() => {
const storage = sessionStorage;
const job = document.getElementById('job');
const position = document.getElementById('position');
const skill = document.getElementById('skill');
const table = document.getElementById('table');
document.getElementById('save').addEventListener(
'click',
() => {
storage.job = job.value;
storage.position = position.value;
storage.skill = skill.value;
alert('Saved to storage.');
},
false,
);
document.getElementById('view').addEventListener(
'click',
() => {
if (storage.length < 1) {
alert('Storage is empty.');
return;
}
table.innerHTML = '';
for (let i = 0, len = storage.length; i < len; i++) {
const key = storage.key(i);
const tr = document.createElement('tr');
const td_key = document.createElement('td');
const td_value = document.createElement('td');
td_key.innerHTML = `${key}: `;
td_value.innerHTML = storage[key];
tr.appendChild(td_key);
tr.appendChild(td_value);
table.appendChild(tr);
}
},
false,
);
},
false,
);
ストレージ管理クラスの実装:キーの名前衝突対策
一つのオリジンに複数のアプリを実装し、ローカルストレージを使用する場合、キーが名前衝突することがあります。
そのため、複数アプリを実装する場合は、キーをアプリ名とし、値をオブジェクト(JSON)として保存すると、名前衝突を防止することができます。
ストレージを管理するオブジェクト(サンプルではAppStorage)を作成し、保存データのオブジェクトを隠蔽すると、直感的に使いやすくなります。
HTML
<form>
<label for="job">Job:</label><br>
<input type="text" name="job" id="job" value="Engineer"><br>
<label for="position">Position:</label><br>
<input type="text" name="position" id="position" value="SE"><br>
<label for="skill">Skill:</label><br>
<input type="text" name="skill" id="skill" value="JavaScript">
<input type="button" id="save" value="Save">
<input type="button" id="view" value="View">
</form>
<hr>
<table id="table"></table>
<script src="storage_object.js"></script>
JavaScript
class AppStorage {
constructor(appName) {
this.app = appName;
this.storage = localStorage;
this.data = JSON.parse(this.storage[this.app] || '{}');
this.key = Object.keys(this.data);
}
get(key) {
return this.data[key];
}
set(key, value) {
this.data[key] = value;
}
save() {
this.storage[this.app] = JSON.stringify(this.data);
this.key = Object.keys(this.data);
}
};
const storage = new AppStorage('AppName');
document.addEventListener(
'DOMContentLoaded',
() => {
const job = document.getElementById('job');
const position = document.getElementById('position');
const skill = document.getElementById('skill');
const table = document.getElementById('table');
document.getElementById('save').addEventListener(
'click',
() => {
storage.set('job', job.value);
storage.set('position', position.value);
storage.set('skill', skill.value);
storage.save();
alert('Saved to storage.');
},
false,
);
document.getElementById('view').addEventListener(
'click',
() => {
if (storage.key.length < 1) {
alert('Storage is empty.');
return;
}
table.innerHTML = '';
for (key of storage.key) {
const tr = document.createElement('tr');
const td_key = document.createElement('td');
const td_value = document.createElement('td');
td_key.innerHTML = `${key}: `;
td_value.innerHTML = storage.get(key);
tr.appendChild(td_key);
tr.appendChild(td_value);
table.appendChild(tr);
}
},
false,
);
},
false,
);
storageイベント:ストレージの更新を検知
ローカルストレージでは、別のウィンドウ(同一オリジン)でのストレージの更新を、storageイベントとして検知することができます。
ストレージを更新したウィンドウでは、イベントは発火しません。
セッションストレージは、スコープがウィンドウ単位なので、別のウィンドウとデータを共有することはできません。
storageイベントは、データの更新/反映だけでなく、メッセージ送信(ブロードキャスト)に利用することもできます。
また、メインウィンドウと操作パネルウィンドウを持つWebアプリを実装することもできます。
storageイベントにハンドラを登録するには、window.onstorageプロパティやwindow.addEventListenerメソッドを使用します。
StorageEventのプロパティは、以下になります。
プロパティ | 内容 |
---|---|
key | 変更されたデータのキー |
oldValue | 変更前のデータの値 |
newValue | 変更後のデータの値 |
url | ストレージを更新したドキュメントのURL |
storageArea | 変更されたStorageオブジェクト |
HTML
<form>
<label for="job">Job:</label><br>
<input type="text" name="job" id="job" value="Engineer"><br>
<label for="position">Position:</label><br>
<input type="text" name="position" id="position" value="SE"><br>
<label for="skill">Skill:</label><br>
<input type="text" name="skill" id="skill" value="JavaScript">
<input type="button" id="save" value="Save & View">
</form>
<hr>
<table id="table"></table>
<script src="storage_event.js"></script>
JavaScript
class AppStorage {
constructor(appName) {
this.app = appName;
this.storage = localStorage;
this.data = JSON.parse(this.storage[this.app] || '{}');
this.key = Object.keys(this.data);
}
get(key) {
return this.data[key];
}
set(key, value) {
this.data[key] = value;
}
save() {
this.storage[this.app] = JSON.stringify(this.data);
this.key = Object.keys(this.data);
}
reload() {
this.data = JSON.parse(this.storage[this.app] || '{}');
this.key = Object.keys(this.data);
}
};
const storage = new AppStorage('AppName');
document.addEventListener(
'DOMContentLoaded',
() => {
const job = document.getElementById('job');
const position = document.getElementById('position');
const skill = document.getElementById('skill');
const table = document.getElementById('table');
document.getElementById('save').addEventListener(
'click',
() => {
storage.set('job', job.value);
storage.set('position', position.value);
storage.set('skill', skill.value);
storage.save();
alert('Saved to storage.');
table.innerHTML = '';
for (key of storage.key) {
const tr = document.createElement('tr');
const td_key = document.createElement('td');
const td_value = document.createElement('td');
td_key.innerHTML = `${key}: `;
td_value.innerHTML = storage.get(key);
tr.appendChild(td_key);
tr.appendChild(td_value);
table.appendChild(tr);
}
},
false,
);
window.addEventListener(
'storage',
(evt) => {
alert(`Storage is updated.\n${evt.url}`);
storage.reload();
table.innerHTML = '';
for (key of storage.key) {
const tr = document.createElement('tr');
const td_key = document.createElement('td');
const td_value = document.createElement('td');
td_key.innerHTML = `${key}: `;
td_value.innerHTML = storage.get(key);
tr.appendChild(td_key);
tr.appendChild(td_value);
table.appendChild(tr);
}
},
false,
);
},
false,
);
まとめ
クライアントサイドJavaScriptのWeb Storageについて、解説しました。
Web Storageには、ローカルストレージとセッションストレージがあります。
それぞれの用途は、以下のようになります。
- ローカルストレージ:ページ再開時に使用するデータを保存
- セッションストレージ:ブラウザセッション内でだけ使用するデータを保存