Skip to content

Instantly share code, notes, and snippets.

@juliocesar
Created April 18, 2011 23:19
Show Gist options
  • Save juliocesar/926500 to your computer and use it in GitHub Desktop.
Save juliocesar/926500 to your computer and use it in GitHub Desktop.
This is the best localStorage polyfill in the world
// I mean, seriously, localStorage is supported even by your mum. How about instead of
// casing the feature out, you give users in-memory (stale) storage instead?
// If they close your application, they deserve to lose data anyway.
// if (!('localStorage' in window)) {
if (!Modernizr.localstorage) {
window.localStorage = {
_data : {},
setItem : function(id, val) { return this._data[id] = String(val); },
getItem : function(id) { return this._data.hasOwnProperty(id) ? this._data[id] : undefined; },
removeItem : function(id) { return delete this._data[id]; },
clear : function() { return this._data = {}; }
};
}
@artemdemo
Copy link

@johny-gog is right.
It should be null instead of undefined when there is no id.

@luobotang
Copy link

not a good way.

@enten
Copy link

enten commented Dec 18, 2016

@ArnaudValensi

I don't understand how window.localStorage can be nil if the window.Storage interface exists.
What is the versions of Safari and iOS with which you encountered this issue?

Please tag me on your response sir.

@asfktz
Copy link

asfktz commented Jan 2, 2017

Shouldn't it return null if key is not there?

Thanks for pointing on it, it solve a bug for me.
the case of: JSON.parse(localStorage.getItem('foo'))

@digital-flowers
Copy link

bfff you need to check this polyfill:

if (!window.localStorage) {
  window.localStorage = {
    getItem: function (sKey) {
      if (!sKey || !this.hasOwnProperty(sKey)) { return null; }
      return unescape(document.cookie.replace(new RegExp("(?:^|.*;\\s*)" + escape(sKey).replace(/[\-\.\+\*]/g, "\\$&") + "\\s*\\=\\s*((?:[^;](?!;))*[^;]?).*"), "$1"));
    },
    key: function (nKeyId) {
      return unescape(document.cookie.replace(/\s*\=(?:.(?!;))*$/, "").split(/\s*\=(?:[^;](?!;))*[^;]?;\s*/)[nKeyId]);
    },
    setItem: function (sKey, sValue) {
      if(!sKey) { return; }
      document.cookie = escape(sKey) + "=" + escape(sValue) + "; expires=Tue, 19 Jan 2038 03:14:07 GMT; path=/";
      this.length = document.cookie.match(/\=/g).length;
    },
    length: 0,
    removeItem: function (sKey) {
      if (!sKey || !this.hasOwnProperty(sKey)) { return; }
      document.cookie = escape(sKey) + "=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/";
      this.length--;
    },
    hasOwnProperty: function (sKey) {
      return (new RegExp("(?:^|;\\s*)" + escape(sKey).replace(/[\-\.\+\*]/g, "\\$&") + "\\s*\\=")).test(document.cookie);
    }
  };
  window.localStorage.length = (document.cookie.match(/\=/g) || window.localStorage).length;
}

source: https://developer.mozilla.org/en-US/docs/Web/API/Storage/LocalStorage

@matthewadowns
Copy link

Looks like this Gist needs a new name :o)

I just want to add, there are newer/better/shinier ways to detect if localStorage is functional.
Many browser scenarios disable the features, even if window.localStorage is defined.

A beautiful complete history and solution here:

https://gist.github.com/paulirish/5558557

try {
    localStorage.setItem('mod', 'mod');
    localStorage.removeItem('mod');
    return true;
} catch(e) {
    return false;
}

@icharge
Copy link

icharge commented Jun 14, 2018

From @DmitryBaranovskiy 's snippet.
I just edit some. and can use localStorage['foo'] = 'bar';

(function (isStorage) {
  if (!isStorage) {
    function Storage() { };
    Storage.prototype.setItem = function (id, val) {
      return this[id] = JSON.stringify(val);
    }
    Storage.prototype.getItem = function (id) {
      return this.hasOwnProperty(id) ? this[id] : null;
    }
    Storage.prototype.removeItem = function (id) {
      return delete this[id];
    }
    Storage.prototype.clear = function () {
      var self = this;
      Object.keys(this).map(function (key) { self.removeItem(key) });
    }
    window.localStorage = new Storage();
  }
})((function () {
  try {
    return "localStorage" in window && window.localStorage != null;
  } catch (e) {
    return false;
  }
})());

2018-08-02 Updated

  • return null instead of undefined
  • hasOwnProperty must passed id

@odahcam
Copy link

odahcam commented Jul 18, 2018

Just a observation getItem returns null when it doesn't find any value for the key, not undefined. Returning undefined may cause deserialization errors in any JSON.parse expecting a storage value.

@icharge
Copy link

icharge commented Aug 2, 2018

Updated

@pacotole
Copy link

If you want the simplicity of @juliocesar gist but keep the data in a cookie:

window.localStorage = {
    _data       : JSON.parse(document.cookie.replace(/(?:(?:^|.*;\s*)localStorage\s*\=\s*([^;]*).*$)|^.*$/, "$1") || '{}'),
    _save       : function() { document.cookie = "localStorage=" + JSON.stringify(this._data) + "; expires=Tue, 19 Jan 2038 03:14:07 GMT; path=/"; },
    setItem     : function(id, val) { this._data[id] = String(val); this._save(); },
    getItem     : function(id) { return this._data.hasOwnProperty(id) ? this._data[id] : null; },
    removeItem  : function(id) { delete this._data[id]; this._save(); },
    clear       : function() { this._data = {}; this._save(); }
};

@jonathan-annett
Copy link

jonathan-annett commented Dec 28, 2019

I swear this is hand crafted (not minified)
i made it by editing the first post in this thread, hacking it about until it acheived full functionality in both node.js and chrome.

caveat - it uses proxies to acheive localStorage.item = value functionality. this might mean older browsers choke at it. which probably makes this whole effort nothing but an academic exercise.

it works with Object.keys, and you can get the keys by calling localStorage.keys() also

to prove it works (in chrome at least), you need to do "delete window.localStorage" before loading the script, or edit the argument at the end of the file to give it a different name

in node.js use

var localStorage = require("./localStorage.js") - or whatever you called it.

data is writen to ./localStorage.json in real time

in the browser it will just auto populate window.localStorage when the script loads, and saves data to the cookie "localStorage' - of courese.

for what it's worth, it coverts keynames into a hash of sorts - more of an alphanumeic index. this means you can literally use any string as a key, provided you use the getItem and setItem mechanism.

enjoy.

            (function (o,s,n,b,j,R,K,I,P,T,F,X,C,O,N,A) {
        if (!o.k) {
            var S=function(){},i,c,w=j.stringify[b](j),r=j.parse[b](j),u,
                z=function(x){delete o[x];},D=o.d,
                k=function(k){
                    i=K[I](k);if(i<0){i= K[n];K.push(k);}
                    return i[s](R); 
                },f=function(k,f){P[k]={enumerable:false,configurable:true,value:f};},
                
            L=o.l,LS,ls={},p,h;
            f("ikeys",function(){return P.keys.value().map(function(k,i){return i[s](R);});});
            f("keys",function(){return K.filter(function(k,i){return !!o[i[s](R)];});});
            f("setItem",function(i,v){c=k(i);o[c]=w(v);S();return o[c];});
            f("getItem",function(i){c=K[I](i);h=c[s](R);return c<0||!o[h]?u:r(o[h]);});
            f("removeItem",function(i){c=K[I](i);h=c[s](R);if(c>=0){i=o[h];z(c);S();return i;}});
            f("clear",function(){c=K[n];K.splice(0,c);for(i=0;i<c;i++){z(i[s](R));}S();});
            O.defineProperties(ls,P);
            p={
                get : function(x,k){ return P[k]?P[k].value:ls.getItem(k);},
                set : function(x,k,v){ if(!P[k]){ls.setItem(k,v);return true;}},
                ownKeys: function(){return K.filter(function(k,i){return !!o[i[s](R)];});},
                getOwnPropertyDescriptor : function(k) {
                  return {enumerable: true,configurable: true,};
                }
            };
            LS = new Proxy(ls,p);  
            o.x=function(i){O.keys(i).forEach(function(k){p.set(ls,k,i[k]);});};
            if (o.n) {
                module.exports=LS;
                A="./"+L+".json";
                N=require("fs");
                C=S;
                try{o.x(N.existsSync(A)?r(N.readFileSync(A)):{});}catch(e){}
                S=function(){
                   c=w(LS);
                   i=c.length>2;
                   D=i?"writeFile":"unlink";
                   N[ D ]("./"+L+".json",i?c:C,i?C:u);
                };
               
            } else {
                o.w[o.l]=LS;
                  c = D[C][X]( ';' ).map( function( x ) { return x.trim()[X]( '=' ); } ).reduce( 
                  function( a, b ) { a[ b[ 0 ] ] = b[ 1 ]; return a; }, {} )[ L ];
                  if (c) {
                      o.x(r(atob(c)));
                  }
                  S=function(){
                     c=w(LS);i=c.length>2;
                     D[C]= L+"="+ (i ? btoa(c) : "")+"; ; expires="+ ( i ? N : A) +" UTC; path=/"; 
                  };
            }
            "lwndkx"[X]('').forEach(z);
        }
    })((function (o,k) {
        try {
            o.l=k;
            o.w=window;
            o.d=document; 
            o.k=(k in o.w&&o.w[k]!==null);
        } catch (e) {
            o.w={};
            o.n=!0;
        }
        return o;
    })({},"localStorage"),"toString","length","bind",JSON,36,[],"indexOf",{},!0,!1,"split","cookie",Object,"Tue, 19 Jan 2038 03:14:07","Thu, 01 Jan 1970 00:00:00");

@juliocesar
Copy link
Author

I'm basically terrified looking at the traffic this has had over the years and I never received even a single notification for any of it.

Thanks, people! Yes, my original example is terrible.

@arcreative
Copy link

Not sure if this addresses the fact that private Safari threw an error on setItem in some versions... which is stupid, but still relevant. I'm just going to conveniently ignore it at this point though, and mute all my sentry/rollbar issues :-)

@josephrocca
Copy link

For those that want to "polyfill" the localStorage object due to it being blocked by an error like this: DOMException: Failed to read the 'localStorage' property from 'Window': Access is denied for this document. (usually due to an embedded frame when your site is used in incognito), you'll need to use defineProperty. For example, here's a super minimal polyfill that doesn't allow usage like localStorage.setItem("key", value), but does allow localStorage.key=value:

Object.defineProperty(window, 'localStorage', {value: new Proxy({}, {set:(obj,k,v)=>obj[k]=String(v)}) });

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment