Most JavaScript developers are keenly aware of what they add to the global object and do their best to namespace their work or sequester it in closures. Namespacing and closures reduce the likelihood that necessary functions and variables will be accidentally overwritten, causing errors to be thrown and interfaces to break. Unfortunately, the localStorage
API (available in most modern browsers) doesn’t inherently support creating isolated caches for each script because the cache is site-specific and consists simply of key-value pairs. Internet Explorer’s userData
behavior (which is available all the way back to IE5) does support sequestering the cache to a degree because you need to provide a name for it, but the API doesn’t make a whole lot of sense and isn’t at all equivalent to localStorage
.
Using the native APIs, it’s quite easy to accidentally overwrite an existing key in the cache. Beyond that, a simple call to localStorage.clear()
will wipe out not only your own data, but anything else stored in the local cache. It’s not good.
While working on eCSStender’s implementation of client-side caching, I came to realize the problems with the current state of things and sought to address them by implementing faux namespacing via prefixed keys. I’ve since copied that code out of eCSStender and created a small library named Squirrel.js that not only evens out the differences between localStorage
and userData
, but also makes it easier to manage your client-side data store in a manner unlikely to cause issues with other scripts also using client-side caching.
Here is a quick rundown of how Squirrel.js works:
// create a Squirrel instance | |
var $S = new Squirrel( 'scale-song' ); | |
// write a value to the cache | |
$S.write( 'doe', 'ray' ); | |
// read it back | |
$S.read( 'doe' ); // 'ray' | |
// write a value to a sub-cache | |
$S.write( 'song', 'doe', 'a dear, a female dear' ); | |
// read back the original value | |
$S.read( 'doe' ); // 'ray' | |
// read back the sub-cached value | |
$S.read( 'song', 'doe' ); // 'a dear, a female dear' | |
// removing a single property from the sub-cache | |
$S.remove( 'song', 'doe' ); | |
// try to read the sub-cached value | |
$S.read( 'song', 'doe' ); // null | |
// read the root value | |
$S.read( 'doe' ); // 'ray' | |
// add some more content to the sub-cache | |
$S.write( 'song', 'doe', 'a dear, a female dear' ); | |
$S.write( 'song', 'ray', 'a drop of golden sun' ); | |
// clear the whole sub-cache | |
$S.clear( 'song' ); | |
// check that it's been cleared | |
$S.read( 'song', 'doe' ); // null | |
$S.read( 'song', 'ray' ); // null | |
// check that the root value's still instact | |
$S.read( 'doe' ); // 'ray' | |
// remove a property form the main cache | |
$S.remove( 'doe' ); | |
// check it's value | |
$S.read( 'doe' ); // null | |
// write a bit more data in the root and in a sub-cache | |
$S.write( 'doe', 'ray' ); | |
$S.write( 'song', 'doe', 'a dear, a female dear' ); | |
$S.write( 'song', 'ray', 'a drop of golden sun' ); | |
// clear the whole cache | |
$S.clear(); | |
// check it's all gone | |
$S.read( 'song', 'doe' ); // null | |
$S.read( 'song', 'ray' ); // null | |
$S.read( 'doe' ); // null |
For more, check out the Github page. Feel free to let me know your thoughts on how easy it is to use and how it can be improved.
Comments
“a simple call to localStorage.clear() will wipe out not only your own data, but anything else stored in the local cache”
Obviously that would be a horrible thing, and I don’t think you’re correct here. A quick test (Chrome 6 atm) indicates .clear() only acts on the origin-specific storage.
I should have been more clear: localStorage.clear() is indeed domain-specific, but scripts can accidentally clobber each other’s caches because there’s no sandboxing mechanism.