我想在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 Web存储规范的误解,而不是浏览器特定的错误或限制。

我已经试图理解2公共基础设施中描述的结构化克隆算法。我不完全理解它的意思,但也许我的问题与对象的财产不可枚举有关(???)。

有简单的解决方法吗?


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


当前回答

循环引用

在这个答案中,我专注于具有循环引用的仅数据对象(没有函数等),并开发maja和mathheadinclouds提到的想法(我使用了他的测试用例和我的代码短了几倍)。

实际上,我们可以使用带有适当替换符的JSON.stringify-如果源对象包含对某个对象的多个引用,或者包含循环引用,那么我们可以通过特殊路径字符串(类似于JSONPath)来引用它。

//JSON.strify具有circ-ref的对象的替换器函数refReplacer(){设m=new Map(),v=new Map),init=null;返回函数(字段,值){让p=m.get(this)+(Array.isArray(this)`[${field}]“:”.“+字段);让isComplex=value==对象(值)如果(isComplex)m.set(值,p);让pp=v.get(value)||'';let path=p.replace(/未定义\.\?/,“”);让val=pp`#REF:${pp[0]==“[”?“$”:“$.”}${pp}“:value;!初始化?(init=value):(val===init?val=“#REF:$”:0);if(!pp&&isComplex)v.set(value,path);返回值;}}// ---------------//测试// ---------------//生成具有重复/循环引用的obj让obj={我:{五十: {v:“lorem”},R: {v:“ipsum”}},R:{五十: {v:“dolor”},R:{五十: {v:“坐”},R: {v:“amet”}}}}obj.R.L.uncle=对象L;obj.R..uncle=对象L;obj.R.L..uncle=obj.R.L;obj.R.R..uncle=obj.R.L;obj.L.L.uncle=对象R;obj.L.R.unncle=对象R;testObject=obj;让json=json.stringify(testObject,refReplacer(),4);console.log(“测试对象\n”,testObject);console.log(“带有JSONpath引用的JSON”,JSON);

使用类似JSONpath的引用解析此类JSON内容:

//使用对象的JSONpath引用解析JSON内容函数parseRefJSON(json){let objToPath=新映射();let pathToObj=新映射();let o=JSON.parse(JSON);让遍历=(父级,字段)=>{让obj=父级;let path=“#REF:$”;if(字段!==未定义){obj=父[字段];path=objToPath.get(父)+(Array.isArray(父)`[${field}]`:`${field?'。'+字段:''}`);}objToPath.set(obj,路径);pathToObj.set(路径,对象);let ref=路径目标获取(obj);如果(ref)父[字段]=ref;for(让f在obj中)if(obj==对象(obj))遍历(obj,f);}横向(o);返回o;}// ---------------//测试1// ---------------让json=`{“L”:{“L”:{“v”:“lorem”,“叔叔”:{“L”:{“v”:“dolor”,“叔叔”:“#REF:$.L”},“R”:{“L”:{“v”:“坐”,“叔叔”:“#REF:$.L.L叔叔.L”},“R”:{“v”:“amet”,“叔叔”:“#REF:$.L.L叔叔.L”},“叔叔”:“#REF:$.L”}}},“R”:{“v”:“ipsum”,“叔叔”:“#REF:$.L.L叔叔”}},“R”:“#REF:$.L.L叔叔”}`;let testObject=parseRefJSON(json);console.log(“测试对象\n”,testObject);// ---------------//测试2// ---------------console.log('来自mathheadinclouds的测试答案:');let recovered=测试对象;let obj={//原始对象我:{五十: {v:“lorem”},R: {v:“ipsum”}},R:{五十: {v:“dolor”},R:{五十: {v:“坐”},R: {v:“amet”}}}}obj.R.L.uncle=对象L;obj.R..uncle=对象L;obj.R.L..uncle=obj.R.L;obj.R.R..uncle=obj.R.L;obj.L.L.uncle=对象R;obj.L.R.unncle=对象R;[目标L.L.v===恢复L.L.v,目标L.R.v===恢复L.R.v,目标R.L.v===恢复的R.L.v,目标R.R.L.v===恢复的R.R.L.v,obj.R.R.R.v===恢复的R.R.R.v,obj.R.L.包含==obj.L,对象.R.R.uncle==对象L,obj.R.L.包含==obj.R.L,obj.R.R.包含==obj.R.L,对象.L.L.uncle==对象R,obj.L.R.包含==obj.R,recovered.R.L叔叔==已恢复.L,恢复.R.R.叔叔==恢复.L,恢复.R.R.L叔叔==恢复.R.L,恢复.R.R.R.叔叔==恢复.R.L,recovered.L.L叔叔==恢复。R,已恢复.L.R叔叔==已恢复.R].forEach(x=>console.log('测试通过:'+x));

要将生成的JSON内容加载/保存到存储中,请使用以下代码:

localStorage.myObject = JSON.stringify(testObject, refReplacer());  // Save
testObject = parseRefJSON(localStorage.myObject);                   // Load

其他回答

我在读到另一篇文章后发表了这篇文章,这篇文章的标题是“如何在本地存储中存储数组?”。这很好,除了两个线程都没有提供关于如何在localStorage中维护数组的完整答案之外,不过我已经根据两个线程中包含的信息制定了一个解决方案。

因此,如果其他人希望能够在数组中推送/弹出/移动项目,并且他们希望将该数组存储在localStorage或sessionStorage中,那么可以这样做:

Storage.prototype.getArray = function(arrayName) {
  var thisArray = [];
  var fetchArrayObject = this.getItem(arrayName);
  if (typeof fetchArrayObject !== 'undefined') {
    if (fetchArrayObject !== null) { thisArray = JSON.parse(fetchArrayObject); }
  }
  return thisArray;
}

Storage.prototype.pushArrayItem = function(arrayName,arrayItem) {
  var existingArray = this.getArray(arrayName);
  existingArray.push(arrayItem);
  this.setItem(arrayName,JSON.stringify(existingArray));
}

Storage.prototype.popArrayItem = function(arrayName) {
  var arrayItem = {};
  var existingArray = this.getArray(arrayName);
  if (existingArray.length > 0) {
    arrayItem = existingArray.pop();
    this.setItem(arrayName,JSON.stringify(existingArray));
  }
  return arrayItem;
}

Storage.prototype.shiftArrayItem = function(arrayName) {
  var arrayItem = {};
  var existingArray = this.getArray(arrayName);
  if (existingArray.length > 0) {
    arrayItem = existingArray.shift();
    this.setItem(arrayName,JSON.stringify(existingArray));
  }
  return arrayItem;
}

Storage.prototype.unshiftArrayItem = function(arrayName,arrayItem) {
  var existingArray = this.getArray(arrayName);
  existingArray.unshift(arrayItem);
  this.setItem(arrayName,JSON.stringify(existingArray));
}

Storage.prototype.deleteArray = function(arrayName) {
  this.removeItem(arrayName);
}

示例用法-在localStorage数组中存储简单字符串:

localStorage.pushArrayItem('myArray','item one');
localStorage.pushArrayItem('myArray','item two');

示例用法-在sessionStorage阵列中存储对象:

var item1 = {}; item1.name = 'fred'; item1.age = 48;
sessionStorage.pushArrayItem('myArray',item1);

var item2 = {}; item2.name = 'dave'; item2.age = 22;
sessionStorage.pushArrayItem('myArray',item2);

处理数组的常用方法:

.pushArrayItem(arrayName,arrayItem); -> adds an element onto end of named array
.unshiftArrayItem(arrayName,arrayItem); -> adds an element onto front of named array
.popArrayItem(arrayName); -> removes & returns last array element
.shiftArrayItem(arrayName); -> removes & returns first array element
.getArray(arrayName); -> returns entire array
.deleteArray(arrayName); -> removes entire array from storage

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

可以设置对象

$.jStorage.set(key, value)

轻松检索

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

您可以使用localDataStorage透明地存储JavaScript数据类型(Array、BigInt、Boolean、Date、Float、Integer、String和Object)。它还提供了轻量级数据混淆,自动压缩字符串,方便按关键字(名称)和按关键字(值)查询,并通过前缀关键字帮助在同一域内强制执行分段共享存储。

[免责声明]我是实用程序的作者[/免责声明】

示例:

localDataStorage.set( 'key1', 'Belgian' )
localDataStorage.set( 'key2', 1200.0047 )
localDataStorage.set( 'key3', true )
localDataStorage.set( 'key4', { 'RSK' : [1,'3',5,'7',9] } )
localDataStorage.set( 'key5', null )

localDataStorage.get( 'key1' )  // -->   'Belgian'
localDataStorage.get( 'key2' )  // -->   1200.0047
localDataStorage.get( 'key3' )  // -->   true
localDataStorage.get( 'key4' )  // -->   Object {RSK: Array(5)}
localDataStorage.get( 'key5' )  // -->   null

正如您所看到的,基本值受到了尊重。

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

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

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

https://github.com/adrianmay/rhaboo是一个localStorage糖层,允许您编写如下内容:

var store = Rhaboo.persistent('Some name');
store.write('count', store.count ? store.count+1 : 1);
store.write('somethingfancy', {
  one: ['man', 'went'],
  2: 'mow',
  went: [  2, { mow: ['a', 'meadow' ] }, {}  ]
});
store.somethingfancy.went[1].mow.write(1, 'lawn');

它不使用JSON.stringify/parse,因为这对于大型对象来说是不准确和缓慢的。相反,每个终端值都有自己的localStorage条目。

你可能会猜到我可能和rhaboo有关系。