在 HTML5 localStorage 中存储对象

IT技术 javascript html local-storage
2020-12-15 23:47:09

我想在 HTML5 中存储一个 JavaScript 对象localStorage,但我的对象显然正在转换为字符串。

我可以使用 存储和检索原始 JavaScript 类型和数组localStorage,但对象似乎不起作用。他们应该吗?

这是我的代码:

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);

控制台输出是

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

在我看来,该setItem方法在存储之前将输入转换为字符串。

我在 Safari、Chrome 和 Firefox 中看到了这种行为,所以我认为这是我对HTML5 Web Storage规范的误解,而不是特定于浏览器的错误或限制。

我试图理解http://www.w3.org/TR/html5/infrastructure.html 中描述结构化克隆算法我不完全明白它在说什么,但也许我的问题与我的对象的属性不可枚举有关 (???)

有简单的解决方法吗?


更新:W3C 最终改变了他们对结构化克隆规范的看法,并决定更改规范以匹配实现。参见https://www.w3.org/Bugs/Public/show_bug.cgi?id=12111所以这个问题不再是 100% 有效,但答案仍然可能令人感兴趣。

6个回答

再次查看AppleMozillaMozilla文档,该功能似乎仅限于处理字符串键/值对。

一种解决方法是在存储对象之前将其字符串化,然后在检索它时对其进行解析:

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));
如果数据超出容量,@CMS 可以 setItem 抛出一些异常吗?
2021-02-06 23:47:09
如果您必须处理大型数组或对象,这种方法的问题是性能问题。
2021-02-09 23:47:09
@oligofren 是的,但正如maja正确建议的 eval() => ,这是 eval() => 的一个很好的用法,您可以轻松检索函数代码 => 将其存储为字符串,然后 eval() 将其返回:)
2021-02-15 23:47:09
...仅适用于具有循环引用JSON.stringify()的对象,在我们字符串化的对象中将引用的对象扩展到其完整的“内容”(隐式字符串化)。参见:stackoverflow.com/a/12659424/2044940
2021-02-25 23:47:09
请注意任何元数据都将被删除。你只是得到一个带有键值对的对象,所以任何具有行为的对象都需要重建。
2021-03-05 23:47:09

一个变体的小改进

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);
}

由于短路评估如果不在 StoragegetObject()中将立即返回如果(空字符串;无法处理),它也不会抛出异常nullkeySyntaxErrorvalue""JSON.parse()

我在这里看不到局部变量和快捷方式评估的意义(除了微小的性能改进)。如果key不在本地存储,window.localStorage.getItem(key)回报null-它并不会抛出一个“非法进入”异常-和JSON.parse(null)回报null,以及-它并没有抛出异常或者,无论是在铬21,也不每ES 5.1节15.12.2,因为String(null) === "null"它可以被解释为JSON 文字
2021-02-07 23:47:09
这在 IE8 中不起作用,因此如果您需要支持它,最好使用已确认答案中的功能。
2021-02-17 23:47:09
它只是布尔表达式。仅当左侧为真时才评估第二部分。在这种情况下,整个表达式的结果将来自右侧。基于布尔表达式的计算方式,这是一种流行的技术。
2021-02-26 23:47:09
本地存储中的值始终是原始字符串值。因此,此快捷方式评估处理的是某人之前存储""(空字符串)的时间。因为它类型转换为falseand JSON.parse(""),这会引发SyntaxError异常,所以不会被调用。
2021-03-01 23:47:09
我只是想快速添加用法,因为它对我来说不是很清楚: var userObject = { userId: 24, name: 'Jack Bauer' }; 并设置它 localStorage.setObject('user', userObject); 然后从存储中取回它 userObject = localStorage.getObject('user'); 如果需要,您甚至可以存储一组对象。
2021-03-04 23:47:09

您可能会发现使用这些方便的方法扩展 Storage 对象很有用:

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

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

这样,即使在 API 下仅支持字符串,您也可以获得真正想要的功能。

将 CMS 的方法包装到一个函数中是一个好主意,它只需要一个功能测试:一个用于 JSON.stringify,一个用于 JSON.parse,另一个用于测试 localStorage 是否实际上可以设置和检索对象。修改宿主对象不是一个好主意;我宁愿将其视为一种单独的方法,而不是将其视为localStorage.setObject.
2021-02-20 23:47:09
只是我的两分钱,但我很确定像这样扩展供应商提供的对象不是一个好主意。
2021-02-20 23:47:09
如果存储的值为getObject()将引发SyntaxError异常"",因为JSON.parse()无法处理。有关详细信息,请参阅我对古里亚答案的编辑。
2021-02-23 23:47:09

扩展 Storage 对象是一个很棒的解决方案。对于我的 API,我为 localStorage 创建了一个外观,然后在设置和获取时检查它是否是一个对象。

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;
  }
}
这几乎正​​是我所需要的。只需要在注释前添加 if (value == null) { return false } ,否则在检查 localStorage 上的键是否存在时会导致错误。
2021-02-11 23:47:09
好点,我会编辑这个。虽然你不需要空部分,但如果你需要我推荐三个 ===。如果您使用 JSHint 或 JSLint,您将被警告不要使用 ==。
2021-02-15 23:47:09
如果要将键设置为 0、"" 或任何其他转换为 false 的值,则 set 函数将不起作用。相反,您应该写:if (!key || value === undefined) return;这也可以让您为键存储 'null' 值。
2021-02-26 23:47:09
对于非忍者(像我一样),有人可以为此答案提供一个使用示例吗?是:data.set('username': 'ifedi', 'fullname': { firstname: 'Ifedi', lastname: 'Okonkwo'});
2021-02-28 23:47:09
这实际上很酷。同意@FrancescoFrapporti,您需要一个 if 来获取空值。我还添加了一个 ' || value[0] == "[" ' 测试是否存在数组中。
2021-03-06 23:47:09

Stringify 并不能解决所有问题

似乎这里的答案并没有涵盖 JavaScript 中所有可能的类型,所以这里有一些关于如何正确处理它们的简短示例:

//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"

我不建议存储函数,因为eval()它可能会导致有关安全、优化和调试的问题。一般来说,eval()永远不应该在 JavaScript 代码中使用。

私人会员

JSON.stringify()用于存储对象的问题是,这个函数不能序列化私有成员。这个问题可以通过覆盖.toString()方法来解决(在网络存储中存储数据时会隐式调用):

//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);

循环引用

另一个stringify无法处理的问题是循环引用:

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

在此示例中,JSON.stringify()将抛出TypeError “将循环结构转换为 JSON”如果应该支持存储循环引用,则JSON.stringify()可以使用第二个参数

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;
    }
});

然而,找到一个有效的存储循环引用的解决方案很大程度上取决于需要解决的任务,恢复这些数据也并非易事。

已经有一些关于 SO 处理这个问题的问题:Stringify (convert to JSON) a JavaScript object with circle reference

因此,不用说 - 将数据存储到 Storage 应该基于简单数据副本的唯一前提不是活动对象。
2021-02-08 23:47:09