问答中心分类: JAVASCRIPT如何在 HTML5 localStorage/sessionStorage 中存储对象
0
匿名用户 提问 27分钟 前

我想在 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 网络存储规范,而不是特定于浏览器的错误或限制。
我试图理解结构化克隆中描述的算法2 通用基础设施.我不完全理解它在说什么,但也许我的问题与我的对象的属性不可枚举(???)有关。
有简单的解决方法吗?

更新:W3C 最终改变了对结构化克隆规范的看法,并决定更改规范以匹配实现。看12111 – 存储对象 getItem(key) 方法的规范与实现行为不匹配.所以这个问题不再是 100% 有效,但答案仍然可能是有趣的。

Nickolay 回复 27分钟 前

顺便说一句,您对“结构化克隆算法”的阅读是正确的,只是在实现结束后规范从仅字符串值更改为此值。我提交了错误bugzilla.mozilla.org/show_bug.cgi?id=538142用 Mozilla 来跟踪这个问题。

Nickolay 回复 27分钟 前

这似乎是 indexedDB 的工作……

Nickolay 回复 27分钟 前

在 localStorage 中存储对象数组怎么样?我面临同样的问题,它被转换为字符串。

Nickolay 回复 27分钟 前

你能改为序列化数组吗?像使用 JSON stringify 存储然后在加载时再次解析?

Nickolay 回复 27分钟 前

您可以使用本地数据存储透明地存储 javascript 数据类型(数组、布尔值、日期、浮点数、整数、字符串和对象)

22 Answers
0
Guria 回答 27分钟 前

一个小的改进变体

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

因为短路评估,getObject()将要立即地返回null如果key不在存储中。它也不会抛出一个SyntaxError例外情况value""(空字符串;JSON.parse()无法处理)。

zuallauz 回复 27分钟 前

我只想快速添加用法,因为我并没有立即清楚:var userObject = { userId: 24, name: 'Jack Bauer' };并设置它localStorage.setObject('user', userObject);然后从存储中取回userObject = localStorage.getObject('user');如果需要,您甚至可以存储对象数组。

Ian Davis 回复 27分钟 前

当您返回 this.getItem(key) && JSON.parse(this.getItem(key)) 时,它是否结合了这两件事?我从未见过这种退货方式。

Guria 回复 27分钟 前

它只是布尔表达式。仅当左侧为真时才评估第二部分。在这种情况下,整个表达式的结果将来自右侧。它是基于布尔表达式评估方式的流行技术。

PointedEars 回复 27分钟 前

我在这里看不到局部变量和快捷方式评估的意义(除了性能改进之外)。如果key不在本地存储中,window.localStorage.getItem(key)返回null– 它确实不是抛出“非法访问”异常 – 和JSON.parse(null)返回null也 – 它确实不是抛出异常,无论是在 Chromium 21 还是 perES 5.1 第 15.12.2 节, 因为String(null) === "null"这可以解释为JSON 文字.

PointedEars 回复 27分钟 前

本地存储中的值始终是原始字符串值。所以这个快捷评估确实处理的是当有人存储""(空字符串)之前。因为它类型转换为falseJSON.parse(""),这会抛出一个SyntaxError异常,不被调用。

Ezeke 回复 27分钟 前

这在 IE8 中不起作用,因此如果您需要支持它,最好使用已确认答案中的功能。

Jordan Arseno 回复 27分钟 前

仅供参考:getObject()函数 barfs 如果值undefined找到它进入localStorage的方式。

Avatar 回复 27分钟 前

要完成答案,因为谁设置可能还想清除存储:localStorage.clear();在 getObject() 之后加上你检查:if(userObject)或者if (!userObject).免责声明:我注意到这一点是因为我先尝试了 undefined 。

dr.Crow 回复 27分钟 前

当值为undefined,也许首先检查值是否是安全的,即value || null这也是安全的JSON.parse(value);

Flimm 回复 27分钟 前

一般来说,像这样对全局进行猴子补丁并不是一个好主意,因为它可能会破坏依赖于Storage没有这些方法,并且在浏览器实现的情况下它不兼容未来setObject或者getObject以后的方法。

0
Justin Voskuhl 回答 27分钟 前

您可能会发现使用这些方便的方法扩展 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 下仅支持字符串。

Garrett 回复 27分钟 前

将 CMS 的方法封装成一个函数是一个好主意,它只需要一个功能测试:一个用于 JSON.stringify,一个用于 JSON.parse,另一个用于测试 localStorage 是否实际上可以设置和检索对象。修改宿主对象不是一个好主意;我宁愿将其视为一种单独的方法,而不是localStorage.setObject.

PointedEars 回复 27分钟 前

这个getObject()会抛出一个SyntaxError如果存储的值是异常"", 因为JSON.parse()无法处理。有关详细信息,请参阅我对 Guria 答案的编辑。

Sethen 回复 27分钟 前

只是我的两分钱,但我很确定像这样扩展供应商提供的对象不是一个好主意。

Flimm 回复 27分钟 前

我完全同意@Sethen。请不要像这样对浏览器实现的全局变量进行猴子补丁。它可能会破坏代码,并且与未来可能发布的浏览器不兼容setObject未来在这个全球范围内的方法。

0
Alex Grande 回答 27分钟 前

为 Storage 对象创建外观是一个很棒的解决方案。这样,您可以实现自己的getset方法。对于我的 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;
  }
}
Francesco Frapporti 回复 27分钟 前

这几乎正​​是我所需要的。只需要在注释前加上 if (value == null) { return false } ,否则在检查 localStorage 上是否存在键时会导致错误。

rob_james 回复 27分钟 前

这实际上很酷。同意@FrancescoFrapporti,您需要一个 if 来获取空值。我还添加了一个’|| value[0] == “[” ‘ 测试以防万一那里有一个数组。

Alex Grande 回复 27分钟 前

好点,我会编辑这个。虽然你不需要 null 部分,但如果你需要,我推荐三个 ===。如果你使用 JSHint 或 JSLint,你会被警告不要使用 ==。

Ifedi Okonkwo 回复 27分钟 前

对于非忍者(比如我),有人可以提供这个答案的用法示例吗?是吗:data.set('username': 'ifedi', 'fullname': { firstname: 'Ifedi', lastname: 'Okonkwo'});?

Ifedi Okonkwo 回复 27分钟 前

确实是的!当我克服了被灌输的欲望时,我拿了代码进行测试,并得到了它。我认为这个答案很棒,因为 1)与接受的答案不同,对字符串数据进行某些检查需要时间,以及 2)与下一个不同,它不会扩展本机对象。

Jimmy T. 回复 27分钟 前

然后有人试图存储一个以 { 开头的字符串

Alex Grande 回复 27分钟 前

@吉米T。这让我彻夜难眠,不知道是否有人会这样做。如果您建议一个更好的正则表达式或以有效的方式测试它是否是正确的 json,我将编辑答案。

Jimmy T. 回复 27分钟 前

你可以把所有东西都串起来

Fredrik_Borgstrom 回复 27分钟 前

如果要将键设置为 0、”” 或任何其他转换为 false 的值,则 set 函数将不起作用。相反,你应该写:if (!key || value === undefined) return;这也将允许您为键存储“null”值。

0
maja 回答 27分钟 前

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()方法(在 Web 存储中存储数据时隐式调用):

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

但是,找到存储循环引用的有效解决方案在很大程度上取决于需要解决的任务,并且恢复此类数据也并非易事。
Stack Overflow 上已经有一些关于处理这个问题的问题:字符串化(转换为 JSON)具有循环引用的 JavaScript 对象

Roko C. Buljan 回复 27分钟 前

因此,不用说 – 将数据存储到 Storage 应该基于唯一的前提副本的简单数据。不是活的对象。

oligofren 回复 27分钟 前

可能会使用自定义toJSON而不是 toString() 这些天。不幸的是,没有用于解析的对称等价物。

maja 回复 27分钟 前

toJSON 不支持没有直接 json 表示的类型,如日期、正则表达式、函数和许多其他在我写完这个答案后添加到 JavaScript 中的新类型。

Peter Mortensen 回复 27分钟 前

为什么前面有“+”localStorage.num(num = +localStorage.num)?

maja 回复 27分钟 前

@PeterMortensen 将存储的字符串转换回数字

0
JProgrammer 回答 27分钟 前

有一个很棒的库包含了许多解决方案,因此它甚至支持旧版浏览器,称为j存储
你可以设置一个对象

$.jStorage.set(key, value)

并轻松检索

value = $.jStorage.get(key)
value = $.jStorage.get(key, "default value")
JProgrammer 回复 27分钟 前

@SuperUberDuper jStorage 需要 Prototype、MooTools 或 jQuery