如何在 HTML5 localStorage 中存储对象?

Posted

技术标签:

【中文标题】如何在 HTML5 localStorage 中存储对象?【英文标题】:How to Store Objects in HTML5 localStorage? 【发布时间】:2011-01-01 22:12:37 【问题描述】:

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

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

有简单的解决方法吗?


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

【问题讨论】:

顺便说一句,您对“结构化克隆算法”的阅读是正确的,只是在实施结束后规范从仅字符串值更改为此值。我向 mozilla 提交了 bug bugzilla.mozilla.org/show_bug.cgi?id=538142 以跟踪此问题。 这似乎是 indexedDB 的工作...... 在localStorage中存储一个对象数组怎么样?我面临同样的问题,它被转换为字符串。 你能改为序列化数组吗?像使用 JSON stringify 存储然后在加载时再次解析? 您可以使用localDataStorage 透明地存储javascript数据类型(数组、布尔值、日期、浮点数、整数、字符串和对象) 【参考方案1】:

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

一种解决方法可以是在存储对象之前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));

【讨论】:

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

您可能会发现使用这些方便的方法扩展 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 如果存储的值为"",则此getObject() 将抛出SyntaxError 异常,因为JSON.parse() 无法处理。有关详细信息,请参阅我对 Guria 答案的编辑。 只是我的两分钱,但我很确定像这样扩展供应商提供的对象不是一个好主意。 我完全同意@Sethen。请不要像这样对浏览器实现的全局变量进行猴子补丁。它可能会破坏代码,并且与将来可能在此全局中提供setObject 方法的浏览器不兼容。【参考方案3】:

对variant 的小改进:

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 evaluation,如果key 不在存储中,getObject()立即返回null。如果value""(空字符串;JSON.parse() 无法处理),它也不会抛出SyntaxError 异常。

【讨论】:

我只是想快速添加用法,因为它对我来说不是很清楚:var userObject = userId: 24, name: 'Jack Bauer' ; 并设置它localStorage.setObject('user', userObject); 然后从存储中取回userObject = localStorage.getObject('user'); 你甚至可以存储一个如果需要,可以使用对象数组。 这只是布尔表达式。仅当左侧为真时才评估第二部分。在这种情况下,整个表达式的结果将来自右侧。它是基于布尔表达式评估方式的流行技术。 我看不到局部变量的意义和这里的快捷评估(除了性能改进之外)。如果key 不在本地存储中,window.localStorage.getItem(key) 返回null——它确实抛出一个“非法访问”异常——并且JSON.parse(null) 也返回null——它确实也不会抛出异常,无论是在 Chromium 21 中还是根据 ES 5.1 section 15.12.2,因为 String(null) === "null" 可以解释为 JSON literal。 本地存储中的值始终是原始字符串值。所以这个快捷评估确实处理的是当有人之前存储""(空字符串)时。因为它类型转换为falseJSON.parse(""),这会引发SyntaxError 异常,所以不会被调用。 这在IE8中是行不通的,所以如果你需要支持的话,最好使用已确认答案中的功能。【参考方案4】:

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

【讨论】:

这几乎正是我所需要的。只需要在注释前加上 if (value == null) return false ,否则在检查 localStorage 上是否存在 key 时会出错。 这其实很酷。同意@FrancescoFrapporti,您需要一个 if 来获取空值。我还添加了一个'|| value[0] == "[" ' 测试万一有一个数组在里面。 好点,我会编辑这个。虽然你不需要 null 部分,但如果你需要,我推荐三个 ===。如果你使用 JSHint 或 JSLint,你会被警告不要使用 ==。 对于非忍者(比如我),有人可以提供这个答案的用法示例吗?是:data.set('username': 'ifedi', 'fullname': firstname: 'Ifedi', lastname: 'Okonkwo'); 如果您想将键设置为 0、"" 或任何其他转换为 false 的值,您的 set 函数将不起作用。相反,你应该写:if (!key || value === undefined) return; 这也可以让你为一个键存储一个 'null' 的值。【参考方案5】:

理论上可以用函数来存储对象:

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;

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

【讨论】:

函数序列化/反序列化不可靠,因为it is implementation-dependent。此外,您希望将c.f[k] = escape(a[k]); 替换为Unicode 安全的c.f[k] = encodeURIComponent(a[k]);,并将eval('b.' + k + ' = ' + unescape(data.f[k])); 替换为b[k] = eval("(" + decodeURIComponent(data.f[k]) + ")");。括号是必需的,因为如果您的函数正确序列化,很可能是匿名的,这不是有效的 /Statement/(因此 eval())会抛出 SyntaxError 异常)。 typeof是一个运算符,不要把它写成一个函数。将typeof(a[k]) 替换为typeof a[k] 除了应用我的建议并强调该方法的不可靠性外,我还修复了以下错误: 1. 并非所有变量都被声明。 2. for-in 没有针对自己的属性进行过滤。 3. 代码风格,包括引用,不一致。 @PointedEars 这有什么实际区别?规范说the use and placement of white space, line terminators, and semicolons within the representation String is implementation-dependent.我没有看到任何功能差异。 @Michael 您引用的部分以Note *in particular* that … 开头。但是返回值规范以An implementation-dependent representation of the function is returned. This representation has the syntax of a FunctionDeclaration. 开头,返回值可以是function foo () ——假设是符合实现。【参考方案6】:

有一个很棒的库,其中包含许多解决方案,因此它甚至支持称为 jStorage 的旧版浏览器

你可以设置一个对象

$.jStorage.set(key, value)

轻松检索

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

【讨论】:

@SuperUberDuper jStorage 需要 Prototype、MooTools 或 jQuery【参考方案7】:

我在点击另一个已关闭的帖子后到达此帖子,该帖子已被关闭 - 标题为“如何在本地存储中存储数组?”。这很好,除了两个线程实际上都没有提供关于如何在 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

【讨论】:

这是一组非常方便的方法,用于操作存储在 localStorage 或 sessionStorage 中的数组,值得称赞的是它比它吸引的更多。 @Andy Lorenz 感谢您抽出宝贵时间分享! 像这样对浏览器提供的全局补丁进行猴子补丁通常不是一个好主意。它可能会导致其他代码中断,并且与未来可能希望在全局中发布自己的同名方法的浏览器不向前兼容。 @Flimm 我同意这样做通常不是一个好主意,但这种观点更多地基于理论而不是实践。例如自从我在 2014 年发帖以来,localStorage 或 sessionStorage 实现中的任何内容都没有发生任何已被破坏的更改。我怀疑他们永远不会诚实。但是,如果这种可能性是某人关心的问题——考虑风险是个人决定,而不是“你应该/不”——我的回答可以很容易地用作实现一个自定义数组类的蓝图,该类包含实际的 localStorage /sessionStorage.【参考方案8】:

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 有关。

【讨论】:

【参考方案9】:

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 circular reference

【讨论】:

因此,不用说 - 将数据存储到 Storage 应该基于简单数据的 副本 的唯一前提。不是活的对象。 现在可能会使用自定义 toJSON 而不是 toString() 。不幸的是,没有用于解析的对称等价物。 toJSON 不支持没有直接 json 表示的类型,例如日期、正则表达式、函数和许多其他在我写完这个答案后添加到 JavaScript 中的新类型。【参考方案10】:

这里是@danott 发布的代码的一些扩展版本

它还会从本地存储中实现 delete 值 并展示了如何添加一个 Getter 和 Setter 层而不是

localstorage.setItem(preview, true)

你可以写

config.preview = true

好的,开始吧:

var PT=Storage.prototype

if (typeof PT._setItem >='u') PT._setItem = PT.setItem;
PT.setItem = function(key, value)

  if (typeof value >='u')//..ndefined
    this.removeItem(key)
  else
    this._setItem(key, JSON.stringify(value));


if (typeof PT._getItem >='u') PT._getItem = PT.getItem;
PT.getItem = function(key)
  
  var ItemData = this._getItem(key)
  try
  
    return JSON.parse(ItemData);
  
  catch(e)
  
    return ItemData;
  


// Aliases for localStorage.set/getItem 
get =   localStorage.getItem.bind(localStorage)
set =   localStorage.setItem.bind(localStorage)

// Create ConfigWrapperObject
var config = 

// Helper to create getter & setter
function configCreate(PropToAdd)
    Object.defineProperty( config, PropToAdd, 
      get: function ()       return (  get(PropToAdd)      ) ,
      set: function (val)              set(PropToAdd,  val ) 
    )

//------------------------------

// Usage Part
// Create properties
configCreate('preview')
configCreate('notification')
//...

// Config Data transfer
//set
config.preview = true

//get
config.preview

// delete
config.preview = undefined

你可以用.bind(...) 去掉别名部分。但是我只是把它放进去,因为知道这一点真的很好。我花了几个小时找出为什么简单的 get = localStorage.getItem; 不起作用

【讨论】:

像这样猴子补丁全局变量通常不是一个好主意。它可能会破坏代码,并且与未来不兼容。【参考方案11】:

建议对此处讨论的许多功能以及更好的兼容性使用抽象库。很多选择:

jStorage 或 simpleStorage localForage alekseykulikov/storage Lawnchair Store.js OMG

【讨论】:

【参考方案12】:

我做了一个不会破坏现有存储对象的东西,而是创建了一个包装器,这样你就可以做你想做的事了。结果是一个普通对象,没有方法,可以像任何对象一样访问。

The thing I made.

如果您希望 1 localStorage 属性具有魔力:

var prop = ObjectStorage(localStorage, 'prop');

如果你需要几个:

var storage = ObjectStorage(localStorage, ['prop', 'more', 'props']);

您对prop内部 storage 的对象所做的一切都会自动保存到localStorage。你总是在玩一个真实的物体,所以你可以做这样的事情:

storage.data.list.push('more data');
storage.another.list.splice(1, 2, another: 'object');

并且每个被跟踪对象内部的新对象都会被自动跟踪。

最大的缺点:它依赖于Object.observe(),因此它对浏览器的支持非常有限。而且它看起来不会很快出现在 Firefox 或 Edge 上。

【讨论】:

Object.observe 现在在所有主流浏览器中都已弃用。【参考方案13】:

另一种选择是使用现有插件。

例如,persisto 是一个开源项目,它为 localStorage/sessionStorage 提供了一个简单的接口,并自动化了表单字段(输入、单选按钮和复选框)的持久性。

(免责声明:我是作者。)

【讨论】:

仍在编写我的自述文件,但 my version 不需要 需要 jQuery,因为它看起来确实需要 jQuery,但它确实提供了处理 jQuery 元素对象的替代方法。我将在不久的将来添加更多内容,因为我会使用它更多,以帮助它进一步处理不同的 jQuery 对象并维护诸如持久数据之类的东西。此外,+1 试图提供更简单的解决方案!而且,它使用了localStroage的所有传统方法; exp: var lsh = new localStorageHelper(); lsh.setItem('bob', 'bill'); 也包括事件。【参考方案14】:

您可以使用ejson 将对象存储为字符串。

EJSON 是 JSON 的扩展,支持更多类型。它支持所有 JSON 安全类型,以及:

日期 (JavaScript Date) 二进制(JavaScript Uint8Array 或 EJSON.newBinary 的结果) 用户定义类型(参见EJSON.addType。例如,Mongo.ObjectID 就是这样实现的。)

所有 EJSON 序列化也是有效的 JSON。例如,带有日期和二进制缓冲区的对象将在 EJSON 中序列化为:


  "d": "$date": 1358205756553,
  "b": "$binary": "c3VyZS4="

这是我使用 ejson 的 localStorage 包装器

https://github.com/UziTech/storage.js

我在包装器中添加了一些类型,包括正则表达式和函数

【讨论】:

【参考方案15】:

我制作了另一个只有 20 行代码的简约包装器,以允许按应有的方式使用它:

localStorage.set('myKey',a:[1,2,5], b: 'ok');
localStorage.has('myKey');   // --> true
localStorage.get('myKey');   // --> a:[1,2,5], b: 'ok'
localStorage.keys();         // --> ['myKey']
localStorage.remove('myKey');

https://github.com/zevero/simpleWebstorage

【讨论】:

【参考方案16】:

您可以使用localDataStorage 透明地存储javascript 数据类型(数组、布尔值、日期、浮点数、整数、字符串和对象)。它还提供轻量级的数据混淆,自动压缩字符串,便于按键(名称)查询和按(键)值查询,并通过为键加前缀帮助在同一域内强制执行分段共享存储。

[DISCLAIMER] 我是该实用程序的作者 [/DISCLAIMER]

例子:

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

如您所见,原始值受到尊重。

【讨论】:

这是一个很棒的资源,正是我所需要的。我正在使用 AngularJS 做 Ionic 应用程序,我需要在 localStorage 中保存某些 javascript 对象,到目前为止,我一直在做 JSON.parse 和 JSON.stringify,它们可以工作,但它比能够更麻烦只需使用像这样的实用程序。我要试试看。【参考方案17】:

对于愿意设置和获取类型化属性的 Typescript 用户:

/**
 * Silly wrapper to be able to type the storage keys
 */
export class TypedStorage<T> 

    public removeItem(key: keyof T): void 
        localStorage.removeItem(key);
    

    public getItem<K extends keyof T>(key: K): T[K] | null 
        const data: string | null =  localStorage.getItem(key);
        return JSON.parse(data);
    

    public setItem<K extends keyof T>(key: K, value: T[K]): void 
        const data: string = JSON.stringify(value);
        localStorage.setItem(key, data);
    

Example usage:

// write an interface for the storage
interface MyStore 
   age: number,
   name: string,
   address: city:string


const storage: TypedStorage<MyStore> = new TypedStorage<MyStore>();

storage.setItem("wrong key", ""); // error unknown key
storage.setItem("age", "hello"); // error, age should be number
storage.setItem("address", city:"Here"); // ok

const address: city:string = storage.getItem("address");

【讨论】:

【参考方案18】:

我找到了一种方法可以让它与具有循环引用的对象一起工作。

让我们用循环引用创建一个对象。

obj = 
    L: 
        L:  v: 'lorem' ,
        R:  v: 'ipsum' 
    ,
    R: 
        L:  v: 'dolor' ,
        R: 
            L:  v: 'sit' ,
            R:  v: 'amet' 
        
    

obj.R.L.uncle = obj.L;
obj.R.R.uncle = obj.L;
obj.R.R.L.uncle = obj.R.L;
obj.R.R.R.uncle = obj.R.L;
obj.L.L.uncle = obj.R;
obj.L.R.uncle = obj.R;

我们不能在这里做JSON.stringify,因为循环引用。

LOCALSTORAGE.CYCLICJSON.stringify.parse 就像普通的 JSON 一样,但适用于具有循环引用的对象。 (“Works”的意思是 parse(stringify(obj)) 和 obj 是深度相等的,并且具有相同的“内等式”集)

但我们可以只使用快捷方式:

LOCALSTORAGE.setObject('latinUncles', obj)
recovered = LOCALSTORAGE.getObject('latinUncles')

那么,recovered 将与 obj “相同”,在以下意义上:

[
obj.L.L.v === recovered.L.L.v,
obj.L.R.v === recovered.L.R.v,
obj.R.L.v === recovered.R.L.v,
obj.R.R.L.v === recovered.R.R.L.v,
obj.R.R.R.v === recovered.R.R.R.v,
obj.R.L.uncle === obj.L,
obj.R.R.uncle === obj.L,
obj.R.R.L.uncle === obj.R.L,
obj.R.R.R.uncle === obj.R.L,
obj.L.L.uncle === obj.R,
obj.L.R.uncle === obj.R,
recovered.R.L.uncle === recovered.L,
recovered.R.R.uncle === recovered.L,
recovered.R.R.L.uncle === recovered.R.L,
recovered.R.R.R.uncle === recovered.R.L,
recovered.L.L.uncle === recovered.R,
recovered.L.R.uncle === recovered.R
]

这里是LOCALSTORAGE的实现

LOCALSTORAGE = (function()
  "use strict";
  var ignore = [Boolean, Date, Number, RegExp, String];
  function primitive(item)
    if (typeof item === 'object')
      if (item === null)  return true; 
      for (var i=0; i<ignore.length; i++)
        if (item instanceof ignore[i])  return true; 
      
      return false;
     else 
      return true;
    
  
  function infant(value)
    return Array.isArray(value) ? [] : ;
  
  function decycleIntoForest(object, replacer) 
    if (typeof replacer !== 'function')
      replacer = function(x) return x; 
    
    object = replacer(object);
    if (primitive(object)) return object;
    var objects = [object];
    var forest  = [infant(object)];
    var bucket  = new WeakMap(); // bucket = inverse of objects 
    bucket.set(object, 0);    
    function addToBucket(obj)
      var result = objects.length;
      objects.push(obj);
      bucket.set(obj, result);
      return result;
    
    function isInBucket(obj) return bucket.has(obj); 
    function processNode(source, target)
      Object.keys(source).forEach(function(key)
        var value = replacer(source[key]);
        if (primitive(value))
          target[key] = value: value;
         else 
          var ptr;
          if (isInBucket(value))
            ptr = bucket.get(value);
           else 
            ptr = addToBucket(value);
            var newTree = infant(value);
            forest.push(newTree);
            processNode(value, newTree);
          
          target[key] = pointer: ptr;
        
      );
    
    processNode(object, forest[0]);
    return forest;
  ;
  function deForestIntoCycle(forest) 
    var objects = [];
    var objectRequested = [];
    var todo = [];
    function processTree(idx) 
      if (idx in objects) return objects[idx];
      if (objectRequested[idx]) return null;
      objectRequested[idx] = true;
      var tree = forest[idx];
      var node = Array.isArray(tree) ? [] : ;
      for (var key in tree) 
        var o = tree[key];
        if ('pointer' in o) 
          var ptr = o.pointer;
          var value = processTree(ptr);
          if (value === null) 
            todo.push(
              node: node,
              key: key,
              idx: ptr
            );
           else 
            node[key] = value;
          
         else 
          if ('value' in o) 
            node[key] = o.value;
           else 
            throw new Error('unexpected')
          
        
      
      objects[idx] = node;
      return node;
    
    var result = processTree(0);
    for (var i = 0; i < todo.length; i++) 
      var item = todo[i];
      item.node[item.key] = objects[item.idx];
    
    return result;
  ;
  var console = 
    log: function(x)
      var the = document.getElementById('the');
      the.textContent = the.textContent + '\n' + x;
	,
	delimiter: function()
      var the = document.getElementById('the');
      the.textContent = the.textContent +
		'\n*******************************************';
	
  
  function logCyclicObjectToConsole(root) 
    var cycleFree = decycleIntoForest(root);
    var shown = cycleFree.map(function(tree, idx) 
      return false;
    );
    var indentIncrement = 4;
    function showItem(nodeSlot, indent, label) 
      var leadingSpaces = ' '.repeat(indent);
      var leadingSpacesPlus = ' '.repeat(indent + indentIncrement);
      if (shown[nodeSlot]) 
        console.log(leadingSpaces + label + ' ... see above (object #' + nodeSlot + ')');
       else 
        console.log(leadingSpaces + label + ' object#' + nodeSlot);
        var tree = cycleFree[nodeSlot];
        shown[nodeSlot] = true;
        Object.keys(tree).forEach(function(key) 
          var entry = tree[key];
          if ('value' in entry) 
            console.log(leadingSpacesPlus + key + ": " + entry.value);
           else 
            if ('pointer' in entry) 
              showItem(entry.pointer, indent + indentIncrement, key);
            
          
        );
      
    
	console.delimiter();
    showItem(0, 0, 'root');
  ;
  function stringify(obj)
    return JSON.stringify(decycleIntoForest(obj));
  
  function parse(str)
    return deForestIntoCycle(JSON.parse(str));
  
  var CYCLICJSON = 
    decycleIntoForest: decycleIntoForest,
    deForestIntoCycle : deForestIntoCycle,
    logCyclicObjectToConsole: logCyclicObjectToConsole,
    stringify : stringify,
    parse : parse
  
  function setObject(name, object)
    var str = stringify(object);
    localStorage.setItem(name, str);
  
  function getObject(name)
    var str = localStorage.getItem(name);
    if (str===null) return null;
    return parse(str);
  
  return 
    CYCLICJSON : CYCLICJSON,
    setObject  : setObject,
    getObject  : getObject
  
)();
obj = 
	L: 
		L:  v: 'lorem' ,
		R:  v: 'ipsum' 
	,
	R: 
		L:  v: 'dolor' ,
		R: 
			L:  v: 'sit' ,
			R:  v: 'amet' 
		
	

obj.R.L.uncle = obj.L;
obj.R.R.uncle = obj.L;
obj.R.R.L.uncle = obj.R.L;
obj.R.R.R.uncle = obj.R.L;
obj.L.L.uncle = obj.R;
obj.L.R.uncle = obj.R;

// LOCALSTORAGE.setObject('latinUncles', obj)
// recovered = LOCALSTORAGE.getObject('latinUncles')
// localStorage not available inside fiddle ):
LOCALSTORAGE.CYCLICJSON.logCyclicObjectToConsole(obj)
putIntoLS = LOCALSTORAGE.CYCLICJSON.stringify(obj);
recovered = LOCALSTORAGE.CYCLICJSON.parse(putIntoLS);
LOCALSTORAGE.CYCLICJSON.logCyclicObjectToConsole(recovered);

var the = document.getElementById('the');
the.textContent = the.textContent + '\n\n' +
JSON.stringify(
[
obj.L.L.v === recovered.L.L.v,
obj.L.R.v === recovered.L.R.v,
obj.R.L.v === recovered.R.L.v,
obj.R.R.L.v === recovered.R.R.L.v,
obj.R.R.R.v === recovered.R.R.R.v,
obj.R.L.uncle === obj.L,
obj.R.R.uncle === obj.L,
obj.R.R.L.uncle === obj.R.L,
obj.R.R.R.uncle === obj.R.L,
obj.L.L.uncle === obj.R,
obj.L.R.uncle === obj.R,
recovered.R.L.uncle === recovered.L,
recovered.R.R.uncle === recovered.L,
recovered.R.R.L.uncle === recovered.R.L,
recovered.R.R.R.uncle === recovered.R.L,
recovered.L.L.uncle === recovered.R,
recovered.L.R.uncle === recovered.R
]
)
&lt;pre id='the'&gt;&lt;/pre&gt;

【讨论】:

【参考方案19】:

没有字符串格式就不能存储键值。

LocalStorage 仅支持键/值的字符串格式。

这就是为什么您应该将数据转换为字符串,无论它是 Array 还是 Object

要在 localStorage 中存储数据,首先使用 JSON.stringify() 方法对其进行字符串化。

var myObj = [name:"test", time:"Date 2017-02-03T08:38:04.449Z"];
localStorage.setItem('item', JSON.stringify(myObj));

那么当你想检索数据时,你需要再次将String解析为Object。

var getObj = JSON.parse(localStorage.getItem('item'));

希望对你有帮助。

【讨论】:

谢谢,我明白了本地存储的概念【参考方案20】:
localStorage.setItem('user', JSON.stringify(user));

然后从存储中检索它并再次转换为对象:

var user = JSON.parse(localStorage.getItem('user'));

If we need to delete all entries of the store we can simply do:

localStorage.clear();

【讨论】:

这是一个有 10 年历史的问题。您认为您的答案是否添加了其他答案尚未涵盖的内容?【参考方案21】:

循环引用

在这个答案中,我专注于带有循环引用的纯数据对象(没有函数等),并开发maja 和mathheadinclouds 提到的想法(我使用他的测试用例和 我的代码要短几倍)。实际上,我们可以将 JSON.stringify 与正确的 replacer 一起使用 - 如果源对象包含对某个对象的多重引用,或者包含循环引用,那么我们通过特殊的路径字符串引用它(类似于 JSONPath)

// JSON.strigify replacer for objects with circ ref
function refReplacer() 
  let m = new Map(), v= new Map(), init = null;

  return function(field, value) 
    let p= m.get(this) + (Array.isArray(this) ? `[$field]` : '.' + field); 
    let isComplex= value===Object(value)
    
    if (isComplex) m.set(value, p);  
    
    let pp = v.get(value)||'';
    let path = p.replace(/undefined\.\.?/,'');
    let val = pp ? `#REF:$pp[0]=='[' ? '$':'$.'$pp` : value;
    
    !init ? (init=value) : (val===init ? val="#REF:$" : 0);
    if(!pp && isComplex) v.set(value, path);
   
    return val;
  



// ---------------
// TEST
// ---------------

// gen obj with duplicate/circular references
let obj = 
    L: 
        L:  v: 'lorem' ,
        R:  v: 'ipsum' 
    ,
    R: 
        L:  v: 'dolor' ,
        R: 
            L:  v: 'sit' ,
            R:  v: 'amet' 
        
    

obj.R.L.uncle = obj.L;
obj.R.R.uncle = obj.L;
obj.R.R.L.uncle = obj.R.L;
obj.R.R.R.uncle = obj.R.L;
obj.L.L.uncle = obj.R;
obj.L.R.uncle = obj.R;
testObject = obj;

let json = JSON.stringify(testObject, refReplacer(), 4);

console.log("Test Object\n", testObject);
console.log("JSON with JSONpath references\n",json);

使用类似 JSONpath 的引用解析此类 json

// parse json with JSONpath references to object
function parseRefJSON(json) 
  let objToPath = new Map();
  let pathToObj = new Map();
  let o = JSON.parse(json);
  
  let traverse = (parent, field) => 
    let obj = parent;
    let path = '#REF:$';

    if (field !== undefined) 
      obj = parent[field];
      path = objToPath.get(parent) + (Array.isArray(parent) ? `[$field]` : `$field?'.'+field:''`);
    

    objToPath.set(obj, path);
    pathToObj.set(path, obj);
    
    let ref = pathToObj.get(obj);
    if (ref) parent[field] = ref;

    for (let f in obj) if (obj === Object(obj)) traverse(obj, f);
  
  
  traverse(o);
  return o;



// ---------------
// TEST 1
// ---------------

let json = `

    "L": 
        "L": 
            "v": "lorem",
            "uncle": 
                "L": 
                    "v": "dolor",
                    "uncle": "#REF:$.L"
                ,
                "R": 
                    "L": 
                        "v": "sit",
                        "uncle": "#REF:$.L.L.uncle.L"
                    ,
                    "R": 
                        "v": "amet",
                        "uncle": "#REF:$.L.L.uncle.L"
                    ,
                    "uncle": "#REF:$.L"
                
            
        ,
        "R": 
            "v": "ipsum",
            "uncle": "#REF:$.L.L.uncle"
        
    ,
    "R": "#REF:$.L.L.uncle"
`;

let testObject = parseRefJSON(json);

console.log("Test Object\n", testObject);


// ---------------
// TEST 2
// ---------------

console.log('Tests from mathheadinclouds anser:');

let recovered = testObject;

let obj =  // original object
    L: 
        L:  v: 'lorem' ,
        R:  v: 'ipsum' 
    ,
    R: 
        L:  v: 'dolor' ,
        R: 
            L:  v: 'sit' ,
            R:  v: 'amet' 
        
    

obj.R.L.uncle = obj.L;
obj.R.R.uncle = obj.L;
obj.R.R.L.uncle = obj.R.L;
obj.R.R.R.uncle = obj.R.L;
obj.L.L.uncle = obj.R;
obj.L.R.uncle = obj.R;

[
  obj.L.L.v === recovered.L.L.v,
  obj.L.R.v === recovered.L.R.v,
  obj.R.L.v === recovered.R.L.v,
  obj.R.R.L.v === recovered.R.R.L.v,
  obj.R.R.R.v === recovered.R.R.R.v,
  obj.R.L.uncle === obj.L,
  obj.R.R.uncle === obj.L,
  obj.R.R.L.uncle === obj.R.L,
  obj.R.R.R.uncle === obj.R.L,
  obj.L.L.uncle === obj.R,
  obj.L.R.uncle === obj.R,
  recovered.R.L.uncle === recovered.L,
  recovered.R.R.uncle === recovered.L,
  recovered.R.R.L.uncle === recovered.R.L,
  recovered.R.R.R.uncle === recovered.R.L,
  recovered.L.L.uncle === recovered.R,
  recovered.L.R.uncle === recovered.R
].forEach(x=> console.log('test pass: '+x));

要将结果 json 加载/保存到存储中,请使用以下代码

localStorage.myObject = JSON.stringify(testObject, refReplacer());  // save
testObject = parseRefJSON(localStorage.myObject);                   // load

【讨论】:

【参考方案22】:

我建议使用Jackson-js,它是一个基于装饰器处理对象的序列化和反序列化同时保留其结构的库。

该库处理所有陷阱,例如循环引用、属性别名等。

使用 @JsonProperty() @JsonClassType() 装饰器简单地描述你的类 使用以下方法序列化您的对象:

const objectMapper = new ObjectMapper();        
localstore.setItem(key, objectMapper.stringify<yourObjectType>(yourObject));

要了解更详细的解释,请在此处查看我的答案:

https://***.com/a/66706365/1146499

这里还有 Jackson-js 教程:

https://itnext.io/jackson-js-powerful-javascript-decorators-to-serialize-deserialize-objects-into-json-and-vice-df952454cf

【讨论】:

【参考方案23】:

这个问题已经从纯 JavaScript 的角度得到了充分的回答,其他人已经注意到 localStorage.getItemlocalStorage.setItem 都没有对象的概念——它们只处理字符串和字符串。这个答案提供了一个 TypeScript 友好的解决方案,它将 others have suggested 包含在纯 JavaScript 解决方案中。

TypeScript 4.2.3

Storage.prototype.setObject = function (key: string, value: unknown) 
  this.setItem(key, JSON.stringify(value));
;

Storage.prototype.getObject = function (key: string) 
  const value = this.getItem(key);
  if (!value) 
    return null;
  

  return JSON.parse(value);
;

declare global 
  interface Storage 
    setObject: (key: string, value: unknown) => void;
    getObject: (key: string) => unknown;
  

用法

localStorage.setObject('ages', [23, 18, 33, 22, 58]);
localStorage.getObject('ages');

说明

我们在Storage 原型上声明了setObjectgetObject 函数——localStorage 是这种类型的一个实例。除了getObject 中的空值处理之外,没有什么特别需要注意的。由于getItem 可以返回null,我们必须提前退出,因为在null 值上调用JSON.parse 会引发运行时异常。

Storage 原型上声明函数后,我们将它们的类型定义包含在全局命名空间中的 Storage 类型上。

注意:如果我们用箭头函数定义这些函数,我们需要假设我们调用的存储对象总是localStorage,这可能不是真的。例如,上面的代码也会为sessionStorage添加setObjectgetObject支持。

【讨论】:

对浏览器提供的全局补丁进行猴子补丁通常不是一个好主意。它可以破坏其他代码,并且不兼容未来。【参考方案24】:
localStorage.setItem('obj',JSON.stringify(name:'Akash')); // Set Object in localStorage
localStorage.getItem('obj'); // Get Object from localStorage

sessionStorage.setItem('obj',JSON.stringify(name:'Akash')); // Set Object in sessionStorage
sessionStorage.getItem('obj'); // Get Object from sessionStorage

【讨论】:

以上是关于如何在 HTML5 localStorage 中存储对象?的主要内容,如果未能解决你的问题,请参考以下文章

html5 的 localStorage 可否设置数据的时效时间

HTML5 localStorage 有用的函数 // JavaScript,TypeScript [关闭]

HTML5 LocalStorage:我还剩下多少空间?

localStorage

HTML5 Localstorage & jQuery:删除以某个单词开头的本地存储键

android html5 localstorage某手机从localstorage中取不到数据