javascript - 存储对象到 HTML5 localStorage

  显示原文与译文双语对照的内容

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

我可以使用 localStorage 存储和检索基元JavaScript类型和数组,但对象似乎并不工作。 他们应该?

下面是我的代码:


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站点存储规范的误解,而不是 browser-specific Bug 或者限制。

http://www.w3.org/TR/html5/infrastructure.html, described. 我试过,使意义上的结构化克隆算法 我不完全理解它的意思,但也许我的问题与我的对象的属性有关()?

是否有简单的解决办法?

时间:

在看苹果Mozilla微软文件这一功能似乎只限于处理字符串键/值对。

一个工作流程可以被以 stringify 你的对象在存储对称密钥之前,并在以后将它的解析当你检索它:


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

对justin的变种稍加改进:


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

由于 short-circuit评估,如果 key 不在存储中,getObject() 将立即返回返回 null 。 它也不会抛出 SyntaxError 异常( 如果 value"" ( 空字符串;JSON.parse() 无法处理) ) 。

更新。添加了标记在注释中提到的变量的变量

使用这些便捷方法扩展存储对象可能会很有用:


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

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

这样你就可以得到你真正想要的功能,即使在API下面,你只支持字符串。

有一个很棒的库封装了许多解决方案,所以它甚至支持旧的浏览器,称为 jStorage

你可以设置一个对象


$.jStorage.set(key, value)

并轻松检索


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

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


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

理论上,可以存储具有函数的对象:


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

但是,函数序列化/反序列化是不可靠的,因为它是 implementation-dependent

你还可以重写默认存储 setItem(key,value)getItem(key) 方法来处理像任何其他数据类型一样的对象/数组。 这样,你就可以简单地调用 localStorage.setItem(key,value)localStorage.getItem(key)

我还没有对它进行过广泛的测试,但对于一个小项目来说,它似乎工作得很好。


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

Storage.prototype._getItem = Storage.prototype.getItem;
Storage.prototype.getItem = function(key)
{ 
 try
 {
 return JSON.parse(this._getItem(key));
 }
 catch(e)
 {
 return this._getItem(key);
 }
}

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() 代码中不应该使用。

私有成员

使用 JSON.stringify() 存储对象的问题是,这个函数不能serialise成员。 这个问题可以通过覆盖 .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;
 }
});

然而,为存储循环引用找到一个高效的解决方案,依赖于需要解决的任务,并且恢复这些数据并不是无关紧要的。

在处理这个问题上已经有了一些问题: Stringify javascript对象,带有循环引用

我在另一个主题上做了一个注释( 如何将数组存储在localStorage中)? ,但在这里再次执行: )

刚刚创建了:

https://gist.github.com/3854049


//Setter
Storage.setObj('users.albums.sexPistols',"blah");
Storage.setObj('users.albums.sexPistols',{ sid :"My Way", nancy :"Bitch" });
Storage.setObj('users.albums.sexPistols.sid',"Other songs");

//Getters
Storage.getObj('users');
Storage.getObj('users.albums');
Storage.getObj('users.albums.sexPistols');
Storage.getObj('users.albums.sexPistols.sid');
Storage.getObj('users.albums.sexPistols.nancy');

...