Web Storage API(localStorage と sessionStorage)
ブラウザで実行される JavaScript では、Web APIs と呼ばれる JavaScript からブラウザを操作するための機能を利用することができる。
この Web APIs には、Web Storage API と呼ばれるブラウザ内蔵のデータストア(保管庫)を利用するための機能も含まれている。
実際に JavaScript のコードから、Web Storage API を利用してブラウザ内蔵のデータストアにアクセスするためには、
- window.localStorage プロパティ
- window.sessionStorage プロパティ
を使用する。
この2つのプロパティは下の画像のように、Window オブジェクトのプロパティとして存在している。ただし「window」の部分は省略可能なため、コードを記述する際には単に localStorage , sessionStorage と書いてもよい。
Storage オブジェクト
2つの Storage インスタンス
ブラウザ内蔵のデータストアにアクセスするには、localStorage / sessionStorage プロパティを使用することを説明した。このどちらのプロパティでアクセスしても、参照されるのは Storage オブジェクトのインスタンスとなっている。
JavaScript のコードを実行する JavaScript エンジンは、コードの実行前に Window オブジェクトを生成する。その際に window.localStorage / window.sessionStorage プロパティのそれぞれに対して、Storage オブジェクトのインスタンスを生成して割り当てているのだと思われる。
どちらも Storage オブジェクトのインスタンスであるが、これら2つのインスタンスは同一ではない。
localStorage / sessionStorage プロパティは、いずれも(異なる)Storage オブジェクトのインスタンスを参照しているわけだが、両者には以下の様な共通する性質と異なる性質が存在する。
localStorage / sessionStorage の共通する性質
共通する性質を次に示す。
- Storage オブジェクトのインスタンスメソッドを使用することができる。
- キーと値のペアを保存するが、キーと値は両方とも文字列でなければならない。
- オブジェクトライクなアクセスは可能だが推奨されていない。
- クッキーよりデータサイズの上限が大きく、サーバーへ送信されない。
- 反復可能ではない。しかし方法は用意されている。
- インスタンスメソッドは同期処理として実行される。
- データはオリジン単位で管理される。
これらについて順番に説明していく。
Storage オブジェクトのインスタンスメソッド
localStorage / sessionStorage プロパティが参照するのは、どちらも Storage オブジェクトのインスタンスであるため、Storage オブジェクトのインスタンスメソッドを使用することができる。
上の画像から、使用できるインスタンスメソッドとして
- setItem() メソッド
- getItem() メソッド
- removeItem() メソッド
- clear() メソッド
- key() メソッド
が存在することが分かる。またインスタンスメソッドの他に、length プロパティ(ゲッター)を持っている。length プロパティは Storage のインスタンスに保存されているアイテム(キーと値のペア)の個数を返却してくれる。
以下で、これらのメソッドについて順番に見ていこう。
setItem() メソッド
このメソッドで Storage のインスタンスにキーと値のペアを格納することができる。キーが既に存在する場合は値を更新する。
const storage = localStorage;
// const storage = sessionStorage;
// setItem(key, value) で key / value を格納する。
storage.setItem('key1', 'value1');
storage.setItem('key2', 'value2');
storage.setItem('key3', 'value3');
// key が既に存在する場合は、value が更新される。
storage.setItem('key3', 'updated value');
localStorage あるいは sessionStorage に格納したデータは、Google Chrome の場合、開発者ツールの「Application」タブの「Local Storage」あるいは「Session Storage」で確認することができる。
getItem() メソッド
このメソッドの引数にキーを渡して呼び出すと、Storage のインスタンスのキーに対応する値が返却される。キーが存在しない場合は null が返却される。
// getItem(key) で key に対応する value を取得できる。
console.log(storage.getItem('key1')); // value1
console.log(storage.getItem('key2')); // value2
console.log(storage.getItem('key3')); // updated value
// 該当する key が存在しない場合は null が返却される。
console.log(storage.getItem('key4')); // null
removeItem() メソッド
このメソッドの引数にキーを渡して呼び出すと、Storage のインスタンスの該当するキーと値のペアが削除される。
// removeItem(key) で該当する key / value のペアが削除される。
storage.removeItem('key1');
console.log(storage.getItem('key1')); // null
clear() メソッド
このメソッドを呼び出すと、Storage のインスタンスからすべてのキーと値のペアを削除することができる。
// clear() ですべての key / value のペアが削除される。
storage.clear();
console.log(storage.getItem('key1')); // null
console.log(storage.getItem('key2')); // null
console.log(storage.getItem('key3')); // null
key() メソッド
このメソッドの引数にインデックス(整数値 0, 1 ,2, …)を渡して呼び出すと、Storage のインスタンス内でそのインデックスに対応するキーが返却される。ただし、キーの順番(インデックスの割り付け)はブラウザの実装に依存する。
したがって、このメソッドを単独で使用することにはあまり意味がなく、length プロパティと組み合わせて使用することで、for ループでキー全体に対して反復処理を行うことができる。
const storage = localStorage;
// const storage = sessionStorage;
storage.clear();
storage.setItem('key1', 'value1');
storage.setItem('key2', 'value2');
storage.setItem('key3', 'value3');
storage.setItem('key4', 'value4');
storage.setItem('key5', 'value5');
for (let i = 0; i < storage.length; i++) {
const key = storage.key(i);
console.log(`${key}: ${storage.getItem(key)}`);
}
// key2: value2
// key4: value4
// key3: value3
// key1: value1
// key5: value5
キーと値のペアを保存する
localStorage / sessionStorage はどちらもその Storage オブジェクトのインスタンスにキーと値のペアを保存するが、キーと値は両方とも文字列でなければならない。
そのため、オブジェクトなどを localStorage や sessionStorage に保存したい場合は、復元可能な文字列に変換してから保存する必要がある。この文字列への変換を行うのに JSON.stringify メソッドを使用することができる。この場合、逆に文字列からオブジェクトを復元するには JSON.parse メソッドを使用する。
ただし、JSON 形式は undefined など扱えないデータ型もあるため、JSON.stringify メソッドで JSON 文字列に変換する時点で、元のオブジェクトを完全に復元できなくなる場合もあるので注意する必要がある。
const storage = localStorage;
// const storage = sessionStorage;
const obj1 = {
a: 0,
b: 'Hello',
c: true,
d: [1, 2, 3]
}
const obj2 = {
a: undefined,
b: [1, undefined, null],
c: null
}
// オブジェクトを JSON 文字列に変換
const json1 = JSON.stringify(obj1);
const json2 = JSON.stringify(obj2);
console.log(json1); // {"a":0,"b":"Hello","c":true,"d":[1,2,3]}
console.log(json2); // {"b":[1,null,null],"c":null}
// storage に保存
storage.setItem('testObj1', json1);
storage.setItem('testObj2', json2);
// storage から読み出し(かつ JSON.parse で JSON 文字列からオブジェクトを復元)
const resultObj1 = JSON.parse(storage.getItem('testObj1'));
const resultObj2 = JSON.parse(storage.getItem('testObj2'));
console.log(resultObj1);
// {a: 0, b: "Hello", c: true, d: Array(3)}
// a: 0
// b: "Hello"
// c: true
// d: (3) [1, 2, 3]
// __proto__: Object
console.log(resultObj2);
// {b: Array(3), c: null}
// b: (3) [1, null, null]
// c: null
// __proto__: Object
オブジェクトライクなアクセス
localStorage や sessionStorage が参照する Storage インスタンスへのデータ(キーと値のペア)の保存・取得には、setItem() メソッドや getItem() メソッドを使用せずに、普通のオブジェクトのプロパティアクセスに使用するドット記法やブラケット記法を使用することもできる。
const storage = localStorage;
// const storage = sessionStorage;
storage.clear();
// ドット記法でもブラケット記法でも保存・取得できる。
storage.setItem('key1', 'value1');
storage.key2 = 'value2';
storage['key3'] = 'value3';
console.log(`key1: ${storage.key1}`);
console.log(`key2: ${storage['key3']}`);
console.log(`key3: ${storage.getItem('key3')}`);
// key1: value1
// key2: value3
// key3: value3
ただし、これらの記法による Storage へのアクセスは推奨されていない。それは、
- Storage インスタンスから参照できるプロパティやメソッド(length, setItem(), key(), toString(), …)と同じ名前をキーとして使用できない。
- 後述する storage イベントを使った Storage インスタンスの変更を検出することができない。
という理由があるためだ。
クッキーよりデータサイズの上限が大きく、サーバーへ送信されない
localStorage や sessionStorage を介してブラウザに保存できるデータのサイズはクッキーより大きく、最大5MBとされている。
また、クッキーはサーバーへの送信に使用されるものだが、localStorage や sessionStorage はブラウザ内蔵のデータストア(保管庫)という機能を提供するためだけのものであり、Storage に保存した内容がサーバーへ送信されることはない。
反復可能ではないが方法は用意されている
Storage オブジェクトは反復可能ではないため、for…of…文で反復処理をすることはできない。またキーの一覧リストを返すようなメソッドも用意されてはいない。
しかし、key() メソッドのところで説明したように、key() メソッドと length プロパティを組み合わせて使用することで、for ループでキー全体に対して反復処理を行えるようになっている。
インスタンスメソッドは同期処理
Storage オブジェクトのインスタンスメソッドは全て同期処理として実行される。つまりメソッドの実行完了を待ってから次の処理が行われる。
console.log('start');
const storage = localStorage;
// const storage = sessionStorage;
storage.setItem('key1', 'value1');
storage.setItem('key2', 'value2');
storage.setItem('key3', 'value3');
console.log('getItem():', storage.getItem('key1'));
console.log('getItem():', storage.getItem('key2'));
console.log('getItem():', storage.getItem('key3'));
console.log('key()', storage.key(0));
console.log('key()', storage.key(1));
console.log('key()', storage.key(2));
storage.removeItem('key2');
console.log('removeItem() called');
console.log('getItem():', storage.getItem('key1'));
console.log('getItem():', storage.getItem('key2'));
console.log('getItem():', storage.getItem('key3'));
storage.clear();
console.log('clear() called');
console.log('getItem():', storage.getItem('key1'));
console.log('getItem():', storage.getItem('key2'));
console.log('getItem():', storage.getItem('key3'));
console.log('end');
// start
// getItem(): value1
// getItem(): value2
// getItem(): value3
// key() key2
// key() key3
// key() key1
// removeItem() called
// getItem(): value1
// getItem(): null
// getItem(): value3
// clear() called
// getItem(): null
// getItem(): null
// getItem(): null
// end
storage イベントによる変更の検出
データはオリジン単位で管理される
オリジンとはWebページにアクセスする際に使用する URL のうち、スキーム(プロトコル)、ホスト(ドメイン)、ポート番号の組み合わせを意味する。(参考:MDN)
このオリジン単位で Storage のデータが管理されるため、ことなるオリジンのデータにはアクセスできない。
localStorage / sessionStorage の異なる性質
次に localStorage と sessionStorage では何が異なるのかを見ていこう。以下で述べるような性質の違いがあることを理解した上で、両者を用途に応じて使い分ける必要がある。
データの有効期限と共有範囲
データの有効期限と共有範囲の違いは以下のようになっている。
- データの有効期限が異なる。
- localStorage では、明示的にデータを削除しない限り、データは永久的に破棄されない。
- sessionStorage では、現在のセッション(現在のブラウザのタブ内)でのみデータは有効であり、タブ(やブラウザ)を閉じるとデータは破棄される。
- データの共有範囲が異なる。
- localStorage では、ブラウザのウィンドウやタブを跨いでデータが共有される。
- sessionStorage では、ブラウザのウィンドウやタブを跨いでデータは共有されない。つまり別のタブを開くと異なる Storage のインスタンスが生成される。
storage イベントによる変更の検出
localStorage のみ、storage イベントによるデータの変更の検出を行うことができる。localStorage は複数のウィンドウやタブで一つの Storage インスタンスを共有するため、別のウィンドウやタブで行われた Storage インスタンスの変更を検出できるようになっている。(参考:MDN)
storage イベントを引き起こすメソッドは以下の3つ。
- setItem() メソッド
- removeItem() メソッド
- clear() メソッド
これらのメソッドによる storage イベントは、localStorage を共有する他のウィンドウやタブにおける Window オブジェクト上で捕捉することができる。
storage イベントにおけるイベントオブジェクトは次のようなプロパティを持っている。
プロパティ | 意味 |
---|---|
key | 変更されたキー |
oldValue | 変更前の値 |
newValue | 変更後の値 |
url | 変更が行われたページの URL |
storageArea | 変更のあった Storage のインスタンス |
// タブごとに異なる番号を振る
let tab = localStorage.getItem('tab') || '0';
tab = String(parseInt(tab) + 1);
localStorage.setItem('tab', tab);
console.log('this tab:', tab);
window.addEventListener('storage', function(e) {
console.log('e.key:', e.key);
console.log('e.oldValue:', e.oldValue);
console.log('e.newValue:', e.newValue);
console.log('e.url:', e.url);
console.log('e.storageArea:', e.storageArea);
}, false);
const storage = localStorage;
// const storage = sessionStorage;
let num = 1;
document.getElementById('button').addEventListener('click', () => {
storage.setItem(`tab${tab}-key`, `value${num}`);
num++;
});