Pytanie Przechowywanie obiektów w localStorage HTML5


Chciałbym przechowywać obiekt JavaScript w HTML5 localStorage, ale mój obiekt najwyraźniej jest konwertowany na ciąg znaków.

Mogę przechowywać i pobierać prymitywne typy i tablice JavaScript za pomocą localStorage, ale obiekty nie działają. Czy powinni?

Oto mój kod:

var testObject = { 'one': 1, 'two': 2, 'three': 3 };
console.log('typeof testObject: ' + typeof testObject);
console.log('testObject properties:');
for (var prop in testObject) {
    console.log('  ' + prop + ': ' + testObject[prop]);
}

// Put the object into storage
localStorage.setItem('testObject', testObject);

// Retrieve the object from storage
var retrievedObject = localStorage.getItem('testObject');

console.log('typeof retrievedObject: ' + typeof retrievedObject);
console.log('Value of retrievedObject: ' + retrievedObject);

Wyjście konsoli

typeof testObject: object
testObject properties:
  one: 1
  two: 2
  three: 3
typeof retrievedObject: string
Value of retrievedObject: [object Object]

Wygląda mi to jak setItem metoda konwertuje dane wejściowe na ciąg przed zapisaniem go.

Widzę to zachowanie w Safari, Chrome i Firefox, więc zakładam, że to moje nieporozumienie HTML5 Web Storage spec, a nie błąd lub ograniczenie specyficzne dla przeglądarki.

Próbowałem zrozumieć sens zbudowany klon algorytm opisany w http://www.w3.org/TR/html5/infrastructure.html. Nie w pełni rozumiem, co to jest, ale może mój problem ma związek z tym, że właściwości mojego obiektu nie są przeliczalne (???)

Czy istnieje łatwe obejście tego problemu?


Aktualizacja: W3C ostatecznie zmieniło zdanie na temat specyfikacji struktury klona i postanowiło zmienić specyfikację, aby pasowała do implementacji. Widzieć https://www.w3.org/Bugs/Public/show_bug.cgi?id=12111. Pytanie to nie jest już w 100% poprawne, ale odpowiedzi nadal mogą być interesujące.


2057
2018-01-06 04:05


pochodzenie


BTW, twoja lektura "algorytmu uporządkowanego klonu" jest poprawna, po prostu specyfikacja została zmieniona z wartości tylko na ciąg do tego po zakończeniu implementacji. Złożyłem błąd bugzilla.mozilla.org/show_bug.cgi?id=538142 z mozilla do śledzenia tego problemu. - Nickolay
Wydaje się to być zadaniem dla indexedDB ... - markasoftware
Co powiesz na przechowywanie tablicy obiektów w localStorage? Mam do czynienia z tym samym problemem, który jest konwertowany na ciąg znaków. - Jayant Pareek
czy możesz zamiast tego serializować tablicę? lubisz sklep z JSON stringify, a następnie parsujesz ponownie po załadowaniu? - Brandito
Możesz użyć localDataStorage do przezroczystego przechowywania typów danych javascript (Array, Boolean, Date, Float, Integer, String i Object) - Mac


Odpowiedzi:


Patrząc na jabłko, Mozilla i Microsoft dokumentacja, wydaje się, że funkcjonalność jest ograniczona do obsługi tylko par klucz / wartość string.

Rozwiązaniem może być stringify Twój obiekt przed jego zapisaniem, a następnie przeanalizuj go, gdy go pobierzesz:

var testObject = { 'one': 1, 'two': 2, 'three': 3 };

// Put the object into storage
localStorage.setItem('testObject', JSON.stringify(testObject));

// Retrieve the object from storage
var retrievedObject = localStorage.getItem('testObject');

console.log('retrievedObject: ', JSON.parse(retrievedObject));

2654
2018-01-06 04:25



obserwuj, że wszelkie metadane zostaną usunięte. otrzymujesz obiekt z parami klucz-wartość, więc każdy obiekt z zachowaniem musi zostać przebudowany. - oligofren
@ CMS może setItem rzucić wyjątek, jeśli dane są ponad pojemność? - Ashish Negi
... dotyczy obiektów tylko z odwołaniami cyklicznymi, JSON.stringify() rozszerza obiekt, do którego się odwołuje, do pełnej "treści" (niejawnie uszeregowane) w obiekcie, który tworzymy. Widzieć: stackoverflow.com/a/12659424/2044940 - CoDEmanX
Problem z tym podejściem to problemy z wydajnością, jeśli musisz obsługiwać duże tablice lub obiekty. - Monkey King
@oligofren prawda, ale jak maja poprawnie zasugerował eval () =>, jest to jeden z dobrych sposobów użycia, możesz łatwo pobrać kod funkcji => zapisać go jako ciąg, a następnie eval () go z powrotem :) - jave.web


Niewielka poprawa na wariant:

Storage.prototype.setObject = function(key, value) {
    this.setItem(key, JSON.stringify(value));
}

Storage.prototype.getObject = function(key) {
    var value = this.getItem(key);
    return value && JSON.parse(value);
}

Z powodu ocena zwarcia, getObject() będzie natychmiast powrót null gdyby key nie jest w pamięci. Nie rzuci również SyntaxError wyjątek, jeśli value jest "" (pusty ciąg; JSON.parse() nie mogę tego znieść).


563
2018-06-30 06:45



Po prostu chcę szybko dodać użycie, ponieważ nie było dla mnie jasne: var userObject = { userId: 24, name: 'Jack Bauer' };  I ustawićlocalStorage.setObject('user', userObject);  Następnie odzyskaj go z magazynuuserObject = localStorage.getObject('user');  Możesz nawet przechowywać tablicę obiektów, jeśli chcesz. - zuallauz
To tylko wyrażenie boolowskie. Druga część jest oceniana tylko jeśli lewa jest prawdą. W takim przypadku wynik całego wyrażenia będzie pochodził z prawej strony. Jest to popularna technika oparta na sposobie oceny wyrażeń boolowskich. - Guria
Nie widzę tutaj punktu zmiennej lokalnej i oceny skrótu (pomijając drobne poprawki wydajności). Gdyby key nie znajduje się w lokalnej pamięci, window.localStorage.getItem(key) zwraca null - to robi nie rzucić wyjątek "Nielegalny dostęp" - i JSON.parse(null) zwraca null również - to robi nie wyrzuć też wyjątek, ani w Chromium 21, ani w ES 5.1 sekcja 15.12.2, bo String(null) === "null" które można interpretować jako JSON dosłowny. - PointedEars
Wartości w Magazynie lokalnym są zawsze prymitywnymi wartościami ciągu. To, co robi ta ewaluacja skrótów, jest wtedy, gdy ktoś jest przechowywany "" (pusty ciąg) przed. Ponieważ konwertuje typ na false i JSON.parse(""), który rzuciłby a SyntaxError wyjątek, nie jest wywoływany. - PointedEars
To nie działa w IE8, więc lepiej jest korzystać z funkcji w potwierdzonej odpowiedzi, jeśli potrzebujesz jej obsługiwać. - Ezeke


Przydatne może się okazać rozszerzenie obiektu Storage za pomocą tych przydatnych metod:

Storage.prototype.setObject = function(key, value) {
    this.setItem(key, JSON.stringify(value));
}

Storage.prototype.getObject = function(key) {
    return JSON.parse(this.getItem(key));
}

W ten sposób uzyskasz funkcjonalność, której naprawdę potrzebujesz, mimo że pod API obsługuje tylko ciągi.


196
2018-01-06 04:42



Zawijanie podejścia CMS do funkcji jest dobrym pomysłem, wymaga tylko testów funkcji: Jeden dla JSON.stringify, jeden dla JSON.parse i jeden dla sprawdzenia, czy localStorage może faktycznie ustawić i pobrać obiekt. Modyfikowanie obiektów hosta nie jest dobrym pomysłem; Wolałbym widzieć to jako oddzielną metodę, a nie jako localStorage.setObject. - Garrett
To getObject() rzuci a SyntaxError wyjątek, jeśli zapisana wartość jest "", bo JSON.parse() nie mogę tego znieść. Zobacz moją edycję odpowiedzi Gurii, by poznać szczegóły. - PointedEars
Tylko moje dwa centy, ale jestem prawie pewien, że nie jest dobrym pomysłem rozszerzanie obiektów dostarczanych przez takiego sprzedawcę. - Sethen


Rozszerzenie obiektu Storage to niesamowite rozwiązanie. Dla mojego API utworzyłem fasadę dla localStorage, a następnie sprawdzam, czy jest to obiekt podczas ustawiania i pobierania.

var data = {
  set: function(key, value) {
    if (!key || !value) {return;}

    if (typeof value === "object") {
      value = JSON.stringify(value);
    }
    localStorage.setItem(key, value);
  },
  get: function(key) {
    var value = localStorage.getItem(key);

    if (!value) {return;}

    // assume it is an object that has been stringified
    if (value[0] === "{") {
      value = JSON.parse(value);
    }

    return value;
  }
}

64
2018-01-21 18:29



To było prawie dokładnie to, czego potrzebowałem. Po prostu musiałem dodać, jeśli (wartość == null) {return false} przed komentarzem, w przeciwnym razie spowodował błąd podczas sprawdzania istnienia klucza na localStorage. - Francesco Frapporti
To całkiem fajne. Zgadzam się z @FrancescoFrapporti, jeśli potrzebujesz wartości null. Dodałem także "|| value [0] == "[" test na wypadek, gdyby tam była tablica. - rob_james
Dobra uwaga, będę to edytować. Chociaż nie potrzebujesz części zerowej, ale jeśli tak, zalecam trzy ===. Jeśli użyjesz JSHint lub JSLint, zostaniesz ostrzeżony przed użyciem ==. - Alex Grande
A dla osób nie będących ninja (takich jak ja) ktoś mógłby podać przykład użycia tej odpowiedzi? Czy to: data.set('username': 'ifedi', 'fullname': { firstname: 'Ifedi', lastname: 'Okonkwo'});? - Ifedi Okonkwo
W rzeczy samej! Kiedy przezwyciężyłem moje pragnienie bycia karmionym łyżeczkami, wziąłem kod do przetestowania i otrzymałem go. Myślę, że ta odpowiedź jest wspaniała, ponieważ: 1) W przeciwieństwie do zaakceptowanej odpowiedzi, zajmuje trochę czasu, aby wykonać pewne sprawdzenia danych ciągu i 2) W przeciwieństwie do następnego, nie rozszerza on natywnego obiektu. - Ifedi Okonkwo


Jest świetna biblioteka, która zawiera wiele rozwiązań, więc obsługuje nawet starsze przeglądarki o nazwie jStorage

Możesz ustawić obiekt

$.jStorage.set(key, value)

I odzyskaj ją z łatwością

value = $.jStorage.get(key)
value = $.jStorage.get(key, "default value")

52
2017-08-23 03:52



$ jest nielegalne !!! - SuperUberDuper
@SuperUberDuper jStorage wymaga Prototype, MooTools lub jQuery - JProgrammer


Stringify nie rozwiązuje wszystkich problemów

Wygląda na to, że odpowiedzi tutaj nie obejmują wszystkich typów, które są możliwe w JavaScript, więc oto kilka krótkich przykładów jak sobie z nimi poradzić:

//Objects and Arrays:
    var obj = {key: "value"};
    localStorage.object = JSON.stringify(obj);  //Will ignore private members
    obj = JSON.parse(localStorage.object);
//Boolean:
    var bool = false;
    localStorage.bool = bool;
    bool = (localStorage.bool === "true");
//Numbers:
    var num = 42;
    localStorage.num = num;
    num = +localStorage.num;    //short for "num = parseFloat(localStorage.num);"
//Dates:
    var date = Date.now();
    localStorage.date = date;
    date = new Date(parseInt(localStorage.date));
//Regular expressions:
    var regex = /^No\.[\d]*$/i;     //usage example: "No.42".match(regex);
    localStorage.regex = regex;
    var components = localStorage.regex.match("^/(.*)/([a-z]*)$");
    regex = new RegExp(components[1], components[2]);
//Functions (not recommended):
    function func(){}
    localStorage.func = func;
    eval( localStorage.func );      //recreates the function with the name "func"

Nie polecam do przechowywania funkcji, ponieważ eval() jest złe może prowadzić do problemów dotyczących bezpieczeństwa, optymalizacji i debugowania.         Ogólnie, eval() nigdy nie powinno się używać w kodzie JavaScript.

Członkowie prywatni

Problem z używaniem JSON.stringify()do przechowywania obiektów jest to, że ta funkcja nie może serializować prywatnych członków. Ten problem można rozwiązać, zastępując parametr .toString() metoda (która jest wywoływana niejawnie podczas przechowywania danych w pamięci masowej):

//Object with private and public members:
    function MyClass(privateContent, publicContent){
        var privateMember = privateContent || "defaultPrivateValue";
        this.publicMember = publicContent  || "defaultPublicValue";

        this.toString = function(){
            return '{"private": "' + privateMember + '", "public": "' + this.publicMember + '"}';
        };
    }
    MyClass.fromString = function(serialisedString){
        var properties = JSON.parse(serialisedString || "{}");
        return new MyClass( properties.private, properties.public );
    };
//Storing:
    var obj = new MyClass("invisible", "visible");
    localStorage.object = obj;
//Loading:
    obj = MyClass.fromString(localStorage.object);

Okólniki

Kolejny problem stringify nie można sobie z nimi poradzić, to odwołania kołowe:

var obj = {};
obj["circular"] = obj;
localStorage.object = JSON.stringify(obj);  //Fails

W tym przykładzie JSON.stringify() rzuci a TypeError  "Konwersja struktury kołowej do JSON".         Jeśli przechowywanie odwołań cyklicznych powinno być obsługiwane, drugi parametr JSON.stringify() może być użyty:

var obj = {id: 1, sub: {}};
obj.sub["circular"] = obj;
localStorage.object = JSON.stringify( obj, function( key, value) {
    if( key == 'circular') {
        return "$ref"+value.id+"$";
    } else {
        return value;
    }
});

Jednak znalezienie wydajnego rozwiązania do przechowywania referencji cyklicznych w dużym stopniu zależy od zadań, które należy rozwiązać, a przywracanie takich danych również nie jest łatwe.

Jest już pewne pytanie dotyczące SO radzenia sobie z tym problemem: Stringify (przekonwertuj do JSON) obiekt JavaScript z odwołaniem cyklicznym


50
2017-11-19 09:51





Używanie obiektów JSON do lokalnego przechowywania:

//ZESTAW

var m={name:'Hero',Title:'developer'};
localStorage.setItem('us', JSON.stringify(m));

//OTRZYMAĆ

var gm =JSON.parse(localStorage.getItem('us'));
console.log(gm.name);

// Iteracja wszystkich lokalnych kluczy i wartości pamięci

for (var i = 0, len = localStorage.length; i < len; ++i) {
  console.log(localStorage.getItem(localStorage.key(i)));
}

// KASOWAĆ

localStorage.removeItem('us');
delete window.localStorage["us"];

29
2017-11-20 07:06





Teoretycznie możliwe jest przechowywanie obiektów z funkcjami:

function store (a)
{
  var c = {f: {}, d: {}};
  for (var k in a)
  {
    if (a.hasOwnProperty(k) && typeof a[k] === 'function')
    {
      c.f[k] = encodeURIComponent(a[k]);
    }
  }

  c.d = a;
  var data = JSON.stringify(c);
  window.localStorage.setItem('CODE', data);
}

function restore ()
{
  var data = window.localStorage.getItem('CODE');
  data = JSON.parse(data);
  var b = data.d;

  for (var k in data.f)
  {
    if (data.f.hasOwnProperty(k))
    {
      b[k] = eval("(" + decodeURIComponent(data.f[k]) + ")");
    }
  }

  return b;
}

Jednak serializacja / deserializacja funkcji jest niewiarygodna, ponieważ jest zależny od wdrożenia.


27
2018-04-05 21:20



Funkcja serializacji / deserializacji jest niewiarygodna, ponieważ jest zależny od wdrożenia. Również chcesz zastąpić c.f[k] = escape(a[k]);  z zabezpieczeniem Unicode c.f[k] = encodeURIComponent(a[k]); i eval('b.' + k + ' = ' + unescape(data.f[k])); z b[k] = eval("(" + decodeURIComponent(data.f[k]) + ")");. Nawiasy są wymagane, ponieważ twoja funkcja, jeśli poprawnie serializowana, prawdopodobnie będzie anonimowa, która nie jest taka jak jest, jest poprawna / Statement / (więc eval()) rzuciłoby a SyntaxError inaczej wyjątek). - PointedEars
I typeof to jest operator, nie pisz tego tak, jakby była funkcją. Zastąpić typeof(a[k]) z typeof a[k]. - PointedEars
Oprócz zastosowania moich sugestii i podkreślenia niewiarygodności podejścia, naprawiłem następujące błędy: 1. Nie wszystkie zmienne zostały zadeklarowane. 2. for-in nie został przefiltrowany dla własnych właściwości. 3. Styl kodu, w tym odwoływanie się, był niespójny. - PointedEars
@PointedEars Jakie to ma znaczenie praktyczne? specyfikacja mówi the use and placement of white space, line terminators, and semicolons within the representation String is implementation-dependent.  Nie widzę żadnych różnic funkcjonalnych. - Michael
@Michael Część, którą cytujesz zaczyna się od Note *in particular* that …. Ale specyfikacja wartości zwracanej zaczyna się od An implementation-dependent representation of the function is returned. This representation has the syntax of a FunctionDeclaration. Wartością zwracaną może być function foo () {} - zakładając a zgodne realizacja. - PointedEars