Block Rockin’ Codes

back with another one of those block rockin' codes

localStorageの挙動と簡単なラッパー

[追記]
テストのソースだけを見られる様に、gistに張りました。
gist:542451



localStorageを使ってちょっとやってみたいことが有るので、まずはlocalStorageを色々使って見ようと思ったのですが、思った以上にブラウザごとの挙動に差があって、イベントどころかだだ値を取り出すだけでも、色々気をつける必要があることが分かりました。

以下は、手元の Mac に有った

の環境で試した結果です。

格納出来るデータ

W3Cの仕様ではJSのオブジェクトであれば一通り格納出来るように定めているらしいですが、現在は単なる文字列しか入らない物が多いようです。
なので、オブジェクトを格納するためには、JSONシリアライズして入れる形になると思います。
Native JSON を使ってやるとこんな感じ、

//シリアライズして格納
var str = JSON.stringify({"hoge":"fuga"});
localStorage.setItem(key,str);

//取り出してでシリアライズ
var tmp = localStorage.getItem(key);
var obj = JSON.parse(tmp);

しかし、これを毎回やるのは面倒くさいので、常にシリアライズとデシリアライズするようにラッパを書こうと思ったわけです。

格納と取得

で、書いていたら色々実装の差異があることが分かって来ました。
例えば、先ほどの格納と取得は、以下の三つのやり方が有るとのこと。

//プロパティ−アクセス
localStorage.key = value;
var value = localStorage.key;

//ハッシュアクセス
localStorage["key"] = value;
var value = localStorage["key"];

//メソッド
localStorage.setItem(key, value);
var value = localStorage.getItem(key);

しかし、これFireFoxでは全て正常に動くようですが、
プロパティーとハッシュの場合、Chrome,Safari ではlengthプロパティーが更新されない、
また、存在しないキーへのアクセスが仕様ではnullなのに対し、Chrome,Safariではundefinedになるようです。

つまり、アクセスは面倒でもメソッドを使うのが吉ということのようです。

ループ

localStorageそのものをループで回して、全体に処理したいという場合、
ほとんど普通のオブジェクトのようにforで回すことができる。と思っていたんです。。
ループは以下のようなものが考えられます。

// localStorage.key(i)でi番目のデータのkeyが取れる。
for (var i=0; i<localStorage.length; i++){
	var k = localStorage.key(i);
	localStorage.getItem(k);
}

//インデックスでkeyを取る
for (var i=0; i<localStorage.length; i++){
	var k = localStorage[i];
	localStorage.getItem(k);
}
	
//イテレーション
for (var k in localStorage){
	localStorage.getItem(k);
}

//jQueryのeachも試してみたw
$.each(localStorage,function(i,k){
	localStorage.getItem(k);
});

で、こうして色々なことが分かったのですが、それはもう説明があまりにも細かくて面倒なのでこれを見て自分で確かめてくださいw
この辺の挙動をQUnitでテストしてます。

学習テスト的に、結構適当に確認しようと書いていたんですが、気がついたら結構膨れ上がりました。
一応以下にホスティングしたんで、色々な環境で見てみてください。



QUnit Test Suite



たぶん一番大事なのは以下2点。

最後のやつが一番ハマったのですが、あくまでも自分の環境では、
safariのkey()の挙動がおかしく、どうもちゃんと実装されていないようです。
しかも、コンソールからだとそもそもfunctionではなく、localStorage.keyとやると、
"value"というstringが戻るという謎仕様な動きをするんですが、自分だけでしょうか?

あと $.eachはちょっといじればFireFoxでは使えそう(つかわないけど)

しかたないので、ブラウザごとの差が分かるようにテストを書いて、それを元にこの三つに限ってはこんな感じでカバー出来ます。

var db ={
	set : function(key, obj){
		localStorage.setItem(key, JSON.stringify(obj));
	},

	get : function(key){
		return JSON.parse(localStorage.getItem(key));
	},

	each : function(fun){
		try{
			for (var i=0; i<localStorage.length; i++){
				var k = localStorage.key(i);
				fun(k,db.get(k));
			}
		}catch(e){
			for (var key in localStorage){
				if(key === 'key') continue;
				fun(key,db.get(key));
			}
		}
	}
};

まあ、そのまま素直に被せただけですが、とりあえず手元の環境では格納と取得とループは出来ます。

使い方はテストを見てください。このラッパーのテストも載せてあります。
ソースはこちら。


jxck / localStorage-test — Bitbucket



イベントはもっと色々罠がありそうな噂を聞いたのですが、どうなんでしょう。

w3cも、分厚い仕様書よりこういうのを提供してくれると、自分は分かりやすくてうれしいんですけどね。
まあ、まだ仕様が策定段階だからメンテするの大変だろうけど。

残念なことに今日はたったこんだけのラッパ書くまでで終わってしまったので、次はこれを使ってもう一歩先に進みたいところです。
まあ、でも使えればlocalStorageは便利だし、重要になって行くと思うので、早く仕様と実装が安定していくこといいですね。


今回の件もおかしなところ等有ったら教えて頂けるとうれしいです。