[HTML5] WebStorageにデータの保存や読み込みを行う

ブラウザの内部にデータを保存する方法として、これまではCookieが使われてきましたがこれは主にサーバとのやり取りで利用する物。そこでJavaScriptから手軽に扱えるデータの保存領域としてHTML5ではWebStorageが登場しました。

今回はこのWebStorageの基本的な利用方法についてまとめます。

WebStorageとは

簡単に言うとブラウザ内部の領域にJavaScriptからデータの保存や読み込みが行えるKVS(Key-Value Store)です。イメージとしては連想配列のような物です。RDBなどで採用されているSQLと比べると非常に簡単に利用できます。

余談ですがブラウザ内部でSQLを使ったデータベース「WebSQL」の仕様がW3Cで検討されていましたが、残念ながら頓挫してしまいました。現在ではJavaScriptからブラウザ内部にデータを保存するためにはWebStorageかIndexedDBのいずれかを利用することになります。

2種類の保存方法

WebStorageにはlocalStorageとsessionStorageの2種類の保存方法があります。

localStorage
ブラウザが終了してもデータはブラウザ内部に保存され続けます。
sessionStorage
再読み込みやページ遷移の間だけブラウザ内部に保存されます。ブラウザを終了する、タブを閉じるなどするとデータは消えてなくなります。(Chromeの場合「閉じたタブを開く」を行うとsessionStorageのデータも復元されるようです)

変わるのはデータの生存期間だけで、それ以外の利用方法は同じです。

保存時の制限

容量

保存可能な領域は概ね5M〜10Mbyteと言われています。ハッキリしないのはブラウザや環境に依存するためです。あまり大きなサイズではないので画像データなどをツッコむとあっという間に破綻するので使い方には注意が必要です。それでもCookieが4kbyte程度であることを考えれば大幅なボリュームアップと言えますね。

またデータを保管する空間はドメイン(オリジン)毎に別れています。example.comで10M、example.orgで10Mとそれぞれ独立して容量を消費することが可能です。サブドメインも考慮されますので、a1.example.com, a2.example.comでは別の物として扱われます。

名前空間

名前空間も同様にドメイン(オリジン)毎に別れていますので、他のサービスとキーが被ることを気にする必要も無く、悪意のある第三者にデータを覗かれるといった心配も通常はありません。もちろんサブドメインも考慮されます。

ただしRDBで言うテーブルのように、ひとつのドメイン内に複数の名前空間を作成することはできません。WebStorageを同一ドメイン内で複数の用途で利用する場合にはキーのネーミングルールを工夫する必要があります。よくあるのは以下のように目的毎に接頭詞(prefix)を付けるやり方でしょうか。

// ユーザーデータ
localStorage.setItem("user.name", "panda");
localStorage.setItem("user.age",  "15");

// 履歴
localStorage.setItem("history.page", "/home/page");
localStorage.setItem("history.date", "2020-08-13");

データ型

保存するデータはすべて文字列であるという点です。もし数値や真偽値(Boolean)などを指定した場合はすべて文字列に変換されます。もし取り出す際にデータ型も復元したい場合はJSON.stringify()などでシリアライズしておく必要があります。詳細は後述します。

具体的な変換方法はブラウザの実装に依存しますがGoogleChromeFireFoxでは次のように変換されます。

数値(Number)
"1", "1234"などの単純な文字列
真偽値(Boolean)
"true", "false"などの文字列
配列
配列[1,2,3]を保存した場合は "1,2,3"のようにカンマで値を区切った文字列
オブジェクト
連想配列の場合は"[object Object]"、特定のオブジェクトを渡した場合は"[object HTMLDocument]"のようにオブジェクト名が入ります。

ブラウザ毎の制限

なお以下のようなブラウザ毎の制約もありますので利用時にはご注意ください。

  • IEでは様々な実装上の問題があり、出来れば使わない方がトラブルに巻き込まれずに済みます。
  • ブラウザで閲覧履歴などが残らないプライベート(シークレット)モードでは動作しない場合があります。
  • iOSSafariは端末の容量が少なくなるとlocalStorageのデータを削除する場合があります。

詳細はCanIUseなどをご覧ください。 caniuse.com

WebStorageの利用方法

WebStorageでのCRUDを行います。localStorageとsessionStorageの記述の違いはオブジェクト名だけ。メソッド名や使い方は同じです。

データを保存する

setItem()にキーと値を指定することで保存できます。値で指定するデータ型は基本的に文字列です。

const key   = "name";
const value = "John";

try{
  // locaStorageに保存
  localStorage.setItem(key, value);

  // sessionStorageに保存
  sessionStorage.setItem(key, value);
}
catch(e){
  console.log(e);
}

setItem()には戻り値がありませんが、ブラウザ内部に保存できなかった場合に例外が発生するため、try〜catchで囲っておく必要があります。例えば書き込み時にハードウェアなどにトラブルがあった場合や、前述の通りWebStorageはそれほど大きな容量が使えないよう制限されていますので、容量制限をオーバーした際などに例外が発生します。

文字列以外を保存する

冒頭でお話した通り、数値を保存した場合でも、取り出す際には文字列型に変換されて返ってきます。つまり以下のコードの実行結果はすべて文字列型となります。正直この仕様を考えた人の気持がわからない(´・ω・`) せめて基本的なデータ型くらいは何もせずとも復元させてw

localStorage.setItem("test1", 123);
localStorage.setItem("test2", true);
localStorage.setItem("test3", [1,2,3]);
localStorage.setItem("test4", {"a":"foo", "b":"bar"});

console.log(
  localStorage.getItem("test1"),  // "123" (文字列)
  localStorage.getItem("test2"),  // "true" (文字列)
  localStorage.getItem("test3"),  // "1,2,3" (文字列)
  localStorage.getItem("test4")   // "[object Object]" (文字列)
);

というわけでgetItem()した値のデータ型も復元したい場合には、事前にデータ型が分かっている場合にはNumber()などでキャストするか、次のようにJSONシリアライズ/デシリアライズしてやる必要があります。

// JSON.stringify()でJSON文字列化
localStorage.setItem("test1", JSON.stringify(123));
localStorage.setItem("test2", JSON.stringify(true));
localStorage.setItem("test3", JSON.stringify([1,2,3]));
localStorage.setItem("test4", JSON.stringify({"a":"foo", "b":"bar"}));

// JSON.parse()で元に戻す
console.log(
  JSON.parse(localStorage.getItem("test1")),  // 123
  JSON.parse(localStorage.getItem("test2")),  // true
  JSON.parse(localStorage.getItem("test3")),  // [1,2,3]
  JSON.parse(localStorage.getItem("test4"))   // {"a":"foo", "b":"bar"}
);

データを取得する

getItem()に取得したいキーを指定することでデータが得られます。もしキーが存在しない場合にはnullが返却されます。

const key = "name"

// locaStorageから取得
const value1 = localStorage.getItem(key);

// sessionStorageから取得
const value2 = sessionStorage.getItem(key);

注意点としては前述の通り文字列として返却される点です。setItem()で数値を保存していた場合も文字列で返ってきますのでこの場合はNumber()などでキャストする必要があります。

データを削除する

const key = "name"

// locaStorageから削除
localStorage.removeItem(key);

// sessionStorageから削除
sessionStorage.removeItem(key);

なお正常に削除されたかどうかはこれだけだと分かりません。常にundefinedが戻り値として返され、例外も発生しません。もしチェックしたい場合はgetItem()などでもう一度取得してnullが返されるか確認する必要があります。

全てのデータを削除する

なお、問答無用で全データを削除するメソッドも用意されています。

// locaStorageから削除
localStorage.clear();

// sessionStorageから削除
sessionStorage.clear();

バルス関数とかで包んでおくと気持ち良さそうですねw ♪このちーへいーせーんー(歌え)

function balse(){
  localStorage.clear();
  // sessionStorage.clear();
  console.log("目がー!目がー!");
}

キーの一覧を取得する

key()に数値を指定することでWebStorageに保存されているキーを順番に取り出すことができます。lengthプロパティを参照することで保存されている個数がわかります。

for (let i=0; i<localStorage.length; i++) {
  const key   = localStorage.key(i);
  const value = localStorage.getItem(key);

  console.log(`${key}:${value}`);
}

キーが存在するか確認する

気の利いたメソッドが用意されていないため、getItem()した際の結果がnullであれば存在しないといった処理を書いてあげます。

/**
 * WebStorageに指定したキーが存在するかチェック
 *
 * @param {string} key
 * @param {string} [type="local"]  "local":localStorage, other:sessionStorage
 * @return {boolean}
 */
function storageExistsKey(key, type="local"){
  const storage = (type === "local")?  localStorage:sessionStorage;
  return( storage.getItem(key) !== null );
}

WebStorageが利用可能か判定する

そもそもWebStorageが実行中のブラウザで使えるかどうか確認する方法ですが、現代では若干難解になっていますw 以下はMDNのサンプルを参考にしたサンプルになります。

手っ取り早くfalseの状態を確認するには、FireFoxでプライベートウィンドウを開いて実行します。

if( storageAvailable() ){
  console.log("localStorage使えるよ!ヽ(=´▽`=)ノ");
}
else{
  console.log("localStorage使えないよ (´;ω;`) ");
}


/**
 * WebStorageが利用可能か判定する
 *
 * @param {string} type "localStorage" or "sessionStorage"
 * @return {boolean}
 */
function storageAvailable(type="localStorage") {
  try {
    const storage = window[type];  // localStorage(sessionStorage)のオブジェクトを取得
    const x = '__storage_test__';  // 試験的に保存する値

    // 試験的にWebStorageを利用する
    storage.setItem(x, x);
    storage.removeItem(x);

    // 問題なく利用できればtrue
    return(true);
  }
  catch(e) {
    // 途中で何らかの例外が発生すればfalse
    return(false);
  }
}

何が面倒にさせているかは、MDNの以下の解説をご覧ください。

ローカルストレージに対応しているブラウザーは、 window オブジェクトに localStorage という名称のプロパティを持っています。しかしさまざまな理由で、プロパティが存在すると主張するだけで例外が発生する可能性があります。ローカルストレージが存在していたとしても、さまざまなブラウザーがローカルストレージを無効化する設定を設けていますので、ローカルストレージが利用できる保証はありません。よってブラウザーがローカルストレージに対応していても、ページ上のスクリプトでは利用できる状態ではない場合があります。 例えば Safari はプライベートブラウジングモードでは、容量が 0 で空のローカルストレージを提供しますので、事実上使用できません。逆に、正規の QuotaExceededError が発生した場合、これはストレージ領域を使い切ったことを意味しますが、ストレージは実際に利用可能です。機能検出時には、これらのシナリオを考慮に入れるべきです。

※MDNより

WebStorageカウンター

簡単なサンプルを用意しました。以下から実際にlocalStorageとsessionStorageをお試しいただけます。

サンプル

以下のページから実際にサンプルを実行できます。 miku3.net

  • 「+」ボタンを押すとカウンターの値が増加します。
  • localStorageはずっと残りますが、sessionStorageはブラウザを終了させる(タブを閉じる)と消えてなくなります。

ソースコード

HTMLとJavaScriptでファイルを分けています。

index.html

<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <title>WebStorage Sample1</title>
  <link rel="stylesheet" href="style.css" media="all">
</head>
<body>

<section id="content">
  <h1>WebStorageカウンター</h1>
  <p>「+」ボタンを押すと、WebStorageの値をインクリメント(+1)します。</p>

  <fieldset>
    <legend>localStorage</legend>
    <input type="text" id="txt-local" class="count">
    <button id="btn-local"><img src="image/plus-solid.svg" width="15"></button>
    <p><small>ブラウザが終了してもデータは残ります</small></p>
  </fieldset>

  <fieldset>
    <legend>sessionStorage</legend>
    <input type="text" id="txt-session" class="count">
    <button id="btn-session"><img src="image/plus-solid.svg" width="15"></button>
    <p><small>ブラウザが終了した段階でデータが消えます</small></p>
  </fieldset>
</section>

<script src="app.js"></script>
</body>
</html>

app.js

/**
 * WebStorageカウンター
 *
 * @author M.Katsube <katsubemakito@gmail.com>
 * @version 1.0.0
 */

//---------------------------------------------------------
// グローバル変数
//---------------------------------------------------------
// テキストボックス
const txtLocal = document.querySelector("#txt-local");
const txtSession = document.querySelector("#txt-session");

//---------------------------------------------------------
// onload
//---------------------------------------------------------
window.onload = () => {
  const key = "html5.webstorage1.count";

  // onload時にすでに保存された値があるならロードする
  addStorage("local", key, 0);
  addStorage("session", key, 0);

  /*
   * localStorage側の「+」ボタンをクリック
   */
  document.querySelector("#btn-local").addEventListener("click", (e)=>{
    addStorage("local", key);
    e.preventDefault();        // submit後の処理をキャンセル(画面遷移しない)
  })

  /*
   * sessionStorage側の「+」ボタンをクリック
   */
  document.querySelector("#btn-session").addEventListener("click", (e)=>{
    addStorage("session", key);
    e.preventDefault();        // submit後の処理をキャンセル(画面遷移しない)
  })
}


/**
 * WebStorageの値に+1する
 *
 * @param  {string} type  "local" or "session"
 * @param  {string} key
 * @param  {number} [plus=1]
 * @return {void}
 */
function addStorage(type, key, plus=1){
  const storage = (type === "local")?  localStorage:sessionStorage;
  const display = (type === "local")?  txtLocal:txtSession;

  // WebStorageから指定キーの値を取得
  const buff = storage.getItem(key);

  //----------------------------------------
  // 指定キーが存在しなければ(初めての実行)
  //----------------------------------------
  if( buff === null ){
    storage.setItem(key, 1);  // 1を保存する
    display.value = 1;        // テキストボックスに表示
  }
  //----------------------------------------
  // 存在する場合は +1 する
  //----------------------------------------
  else{
    // getItem()はデータを文字列として返却するので数字にキャストしてから足す
    const value = Number(buff) + plus;
    storage.setItem(key, value);   // +1した値を保存する
    display.value = value;         // テキストボックスに表示
  }
}

デベロッパーツールで確認する

保存されたlocalStorageとsessionStorageのデータはブラウザのデベロッパーツール(開発者ツール)から確認することができます。

GoogleChromeでは以下の通り「Application」から「Storage」とたどることで表示できます。

ここで注目すべきなのは単に表示するだけではなく、編集や削除できてしまう点です。localStorageの値を書き換えてからブラウザを再読み込みすると実際にその値が反映されるのを確認できます。

つまりlocalStorageに保存した情報は改ざんされている可能性があることを念頭に置く必要があることを示しています。

参考ページ