如何在 HTML5 localStorage/sessionStorage 中存储对象

How to store objects in HTML5 localStorage/sessionStorage

提问人:Kristopher Johnson 提问时间:1/6/2010 最后编辑:Samuel LiewKristopher Johnson 更新时间:10/12/2023 访问量:1437844

问:

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

我试图理解 2 通用基础架构中描述的结构化克隆算法。我不完全理解它在说什么,但也许我的问题与我的对象的属性不可枚举(???)有关。

有没有简单的解决方法?


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

javascript html 本地存储

评论

23赞 Nickolay 1/6/2010
顺便说一句,您对“结构化克隆算法”的解读是正确的,只是在实现出来后,规范从仅字符串值更改为此值。我向mozilla提交了错误 bugzilla.mozilla.org/show_bug.cgi?id=538142 以跟踪此问题。
2赞 markasoftware 8/2/2013
这似乎是 indexedDB 的工作......
1赞 Jayant Pareek 6/10/2017
如何在 localStorage 中存储一个 Object 数组?我面临着同样的问题,它正在转换为字符串。
1赞 brandito 3/12/2018
你能只序列化数组吗?喜欢使用 JSON stringify 存储然后在加载时再次解析?
1赞 Mac 6/11/2018
可以使用 localDataStorage 透明地存储 javascript 数据类型(Array、Boolean、Date、Float、Integer、String 和 Object)

答:

3778赞 Christian C. Salvadó 1/6/2010 #1

再次查看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));

评论

203赞 oligofren 10/8/2013
请注意,任何元数据都将被删除。你只需要得到一个具有键值对的对象,所以任何具有行为的对象都需要重新构建。
7赞 Ashish Negi 3/26/2014
如果数据超出容量,setItem @CMS会抛出一些异常吗?
3赞 CodeManX 7/24/2014
...仅适用于具有循环引用的对象,将引用的对象扩展为我们字符串化的对象中的完整“内容”(隐式字符串化)。请参见:stackoverflow.com/a/12659424/2044940JSON.stringify()
3赞 Mark 10/29/2014
这种方法的问题在于性能问题,如果必须处理大型数组或对象。
4赞 jave.web 7/7/2015
@oligofren 是真的,但正如 maja 正确建议的那样 eval() => ,这是 的一个很好的用法之一,您可以轻松地检索函数代码 =>将其存储为字符串,然后将其 eval() 返回:)
246赞 Justin Voskuhl 1/6/2010 #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 下面仅支持字符串,您也可以获得真正想要的功能。

评论

15赞 Garrett 12/9/2010
将 CMS 的方法包装到一个函数中是个好主意,它只需要一个功能测试:一个用于 JSON.stringify,一个用于 JSON.parse,一个用于测试 localStorage 是否可以设置和检索对象。修改宿主对象不是一个好主意;我宁愿将其视为一种单独的方法,而不是.localStorage.setObject
4赞 PointedEars 10/8/2012
如果存储的值为 ,这将引发异常,因为无法处理该异常。有关详细信息,请参阅我对 Guria 答案的编辑。getObject()SyntaxError""JSON.parse()
15赞 Sethen 7/7/2014
只是我的两分钱,但我很确定像这样扩展供应商提供的对象不是一个好主意。
1赞 Flimm 12/17/2021
我完全同意@Sethen.请不要像这样对浏览器实现的全局变量进行猴子补丁。它可能会破坏代码,并且它与将来可能在此全局中发布方法的浏览器不兼容。setObject
678赞 Guria 6/30/2010 #3

对变体的微小改进:

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

由于短路评估,如果不在存储中,将立即返回。如果 is(空字符串; 无法处理)。getObject()nullkeySyntaxErrorvalue""JSON.parse()

评论

55赞 zuallauz 8/17/2011
我只想快速添加用法,因为对我来说并不清楚: 并设置它 然后从存储中取回它 如果需要,您甚至可以存储一组对象。var userObject = { userId: 24, name: 'Jack Bauer' };localStorage.setObject('user', userObject);userObject = localStorage.getObject('user');
9赞 Guria 11/2/2011
它只是布尔表达式。仅当剩下的一部分为真时,才会评估第二部分。在这种情况下,整个表达式的结果将来自右侧部分。它是一种流行的技术,基于如何计算布尔表达式的方式。
5赞 PointedEars 10/8/2012
我在这里看不到局部变量和快捷方式评估的意义(撇开微小的性能改进不谈)。如果不在本地存储中,则返回 - 它不会抛出“非法访问”异常 - 并且返回 - 它也不会抛出异常,无论是在 Chromium 21 中还是在 ES 5.1 第 15.12.2 节中,因为它可以解释为 JSON 文字keywindow.localStorage.getItem(key)nullJSON.parse(null)nullString(null) === "null"
9赞 PointedEars 10/8/2012
本地存储中的值始终是原始字符串值。因此,此快捷方式评估处理的是之前有人存储(空字符串)的时间。因为它类型转换为 和 ,这会引发异常,所以不会被调用。""falseJSON.parse("")SyntaxError
2赞 Ezeke 1/16/2013
这在 IE8 中不起作用,因此如果您需要支持它,最好使用已确认答案中的函数。
84赞 Alex Grande 1/22/2011 #4

为 Storage 对象创建外观是一个很棒的解决方案。这样,您就可以实现自己的方法。对于我的 API,我为 localStorage 创建了一个外观,然后在设置和获取时检查它是否是对象。getset

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

评论

1赞 Francesco Frapporti 3/9/2012
这几乎正是我所需要的。只需要在注释前添加 if (value == null) { return false },否则在检查 localStorage 上是否存在键时会导致错误。
2赞 rob_james 3/13/2012
这实际上很酷。同意@FrancescoFrapporti您需要一个 if 来表示 null 值。我还添加了一个 ' ||value[0] == “[” ' 测试,以防万一在数组中。
3赞 Ifedi Okonkwo 6/20/2015
对于非忍者(像我一样),有人可以为这个答案提供一个使用示例吗?是吗:?data.set('username': 'ifedi', 'fullname': { firstname: 'Ifedi', lastname: 'Okonkwo'});
1赞 Jimmy T. 2/17/2018
然后有人尝试存储一个以 { 开头的字符串
1赞 Jimmy T. 2/25/2018
你可以把所有东西串起来
32赞 aster_x 4/6/2011 #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;
}

但是,函数序列化/反序列化是不可靠的,因为它依赖于实现

评论

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

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

您可以设置对象

$.jStorage.set(key, value)

并轻松检索它

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

评论

4赞 JProgrammer 8/31/2016
@SuperUberDuper jStorage 需要 Prototype、MooTools 或 jQuery
39赞 Andy Lorenz 5/7/2014 #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

评论

3赞 Velojet 8/21/2017
这是一组非常方便的方法,用于操作存储在 localStorage 或 sessionStorage 中的数组,值得称赞的功劳远远超过它的吸引力。@Andy Lorenz 感谢您抽出宝贵时间分享!
0赞 Flimm 12/17/2021
像这样对浏览器发布的全局进行猴子修补通常不是一个好主意。它可能会导致其他代码中断,并且它与将来可能希望在全局中提供自己同名方法的浏览器不向前兼容。
2赞 Andy Lorenz 12/20/2021
@Flimm我同意这样做通常不是一个好主意,但这种观点更多地基于理论而不是实践。例如,自从我在 2014 年发帖以来,localStorage 或 sessionStorage 实现中没有任何变化。我怀疑他们永远不会诚实。但是,如果这种可能性是某人关心的问题 - 并且考虑风险是个人决定,而不是“你应该/不应该” - 我的答案可以很容易地用作实现自定义数组类的蓝图,该数组类包装实际的localStorage/sessionStorage。
4赞 Adrian May 10/1/2014 #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有关。

83赞 maja 11/19/2014 #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()

通常,不应在 JavaScript 代码中使用。eval()

私人会员

用于存储对象的问题在于,此函数不能序列化私有成员。JSON.stringify()

此问题可以通过覆盖方法(在 Web 存储中存储数据时隐式调用)来解决:.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”。JSON.stringify()TypeError

如果应支持存储循环引用,则可以使用 的第二个参数: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 对象

评论

4赞 Roko C. Buljan 3/24/2020
因此,毋庸置疑,将数据存储到存储中应该基于简单数据副本的唯一前提。不是活动对象。
0赞 oligofren 12/17/2021
这些天可能会使用自定义 toJSON 而不是 toString()。不幸的是,没有用于解析的对称等价物。
0赞 maja 12/18/2021
toJSON 不支持没有直接 json 表示的类型,例如日期、正则表达式、函数和许多其他在我写完此答案后添加到 JavaScript 中的较新类型。
0赞 Peter Mortensen 4/29/2022
为什么在()前面加上“+”?localStorage.numnum = +localStorage.num
0赞 maja 4/29/2022
@PeterMortensen将存储的字符串转换回数字
1赞 Nadu 2/11/2015 #10

以下是 danott 发布的代码的一些扩展版本:

它还将从 localstorage 中实现一个删除值,并演示如何添加 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') //..undefined
    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')
//...

// Configuration Data transfer
// Set
config.preview = true

// Get
config.preview

// Delete
config.preview = undefined

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

评论

0赞 Flimm 12/17/2021
像这样给全局变量打补丁通常不是一个好主意。它可能会破坏代码,并且与未来不兼容。
0赞 Peter Mortensen 4/16/2022
Danott 的回答现在被删除了。它在 2019 年底被版主大量删除答案时被删除,没有任何解释。
17赞 doublejosh 3/12/2015 #11

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

评论

0赞 bdombro 10/3/2023
请修改以包含“为什么”建议使用抽象库,尤其是当有一种非常简单且受支持的方法来编码/解码为字符串(即 JSON.stringify/parse)时,它足以支持 99% 的用例。
1赞 Rudie 11/29/2015 #12

我做了一个不会破坏现有 Storage 对象的东西,但会创建一个包装器,以便您可以做您想做的事。结果是一个普通的对象,没有方法,像任何对象一样具有访问权限。

我做的东西。

如果你想让 1 个属性变得神奇:localStorage

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

如果您需要几个:

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

你对 所做的一切,或者里面的对象都会自动保存到 .你总是在玩一个真实的物体,所以你可以做这样的事情:propstoragelocalStorage

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

被跟踪对象中的每个新对象都将被自动跟踪。

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

评论

2赞 Flimm 12/17/2021
Object.observe现在,在所有主流浏览器中均已弃用。
6赞 mar10 6/17/2016 #13

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

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

persisto features

(免责声明:我是作者。

评论

0赞 SpYk3HH 7/12/2016
仍在使用我的自述文件,但我的版本不需要 jQuery,因为它看起来是 persisto,但它确实提供了处理 jQuery 元素对象的替代方案。在不久的将来,我将添加更多内容,因为我使用它,以帮助它进一步处理不同的jQuery对象并维护持久性数据之类的东西。此外,+1 试图提供更简单的解决方案!此外,它使用了所有的传统方法;经验值: 还包括事件。localStroagevar lsh = new localStorageHelper(); lsh.setItem('bob', 'bill');
7赞 Tony Brix 7/28/2016 #14

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

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

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

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

这是我使用 ejson 的 localStorage 包装器

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

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

2赞 zevero 9/28/2016 #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

14赞 Mac 5/17/2017 #16

localDataStorage 是 HTML5 localStorage API 的同步 JavaScript 接口,它——

  1. 使用数据“类型”(如 Array、BigInt、Boolean、Date、Float、Integer、Object 和 String)透明地设置/获取键值;
  2. 提供轻量级数据混淆;
  3. 智能压缩字符串(节省存储空间);
  4. 有助于稳健的查找,包括按键(名称)查询、按(键)值查询和按存在查询(布尔检查);
  5. 通过在键前加上前缀,在同一域中强制实施分段共享存储;
  6. 允许您在触发 localStorage 更改事件的同一页面/选项卡上响应这些事件;
  7. 在源中广播更改事件,以方便其他窗口/选项卡;
  8. 允许您使用专用的阵列键轻松处理阵列;和
  9. 提供内存密钥(可备份到磁盘)以实现尽可能快的读取时间。

[免责声明]我是实用程序的作者 [/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

正如你所看到的,原始值是被尊重的。

评论

1赞 Nmuta 1/21/2018
这是一个很棒的资源,正是我所需要的。我正在使用 AngularJS 制作 Ionic 应用程序,我需要将某些 javascript 对象保存在 localStorage 中,到目前为止,我一直在做 JSON.parse 和 JSON.stringify,它们可以工作,但这比能够使用这样的实用程序要麻烦一些。我要试试。
0赞 bdombro 10/3/2023
No offense, but your lib seems like overkill for 99% use cases. And to not even mention the lighter approach (e.e. JSON.stringify/parse) or alternative libraries seems misleading to me.
0赞 bdombro 10/3/2023
Sorry, let me be a bit more constructive/positive: This answer could be improved by comparing against alternatives approaches. Why and when does your library best alternatives?
0赞 Mac 10/12/2023
Misleading? No, not really. I'm not on the hook to mention other libraries. The answers directly above and below mention several libraries: I'm sure they're all worthy. JSON.stringify is cited plentifully on this page; no need for me to be redundant about it. My library does some things most others don't, but I don't expect anyone to consider it unless they need it (I know I wouldn't). Beyond that, it's unclear why you're critical of something because it does more than bare minimum. I'd be wasting time offering yet another "me too" solution.
6赞 Flavien Volken 5/30/2018 #17

For TypeScript users willing to set and get typed properties:

/**
 * 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");
1赞 mathheadinclouds 11/13/2019 #18

I found a way to make it work with objects that have cyclic references.

Let's make an object with cyclic references.

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;

We can't do here, because of the circular references.JSON.stringify

circularUncle

LOCALSTORAGE.CYCLICJSON has and just like normal , but works with objects with circular references. ("Works" meaning parse(stringify(obj)) and obj are deep equal AND have identical sets of 'inner equalities').stringify.parseJSON

But we can just use the shortcuts:

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

Then, will be "the same" to obj, in the following sense:recovered

[
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
]

Here is the implementation of 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
]
)
<pre id='the'></pre>

23赞 Moshiur Rahman 1/21/2020 #19

You cannot store a key value without a string format.

LocalStorage only supports string formats for keys/values.

That is why you should convert your data to string whatever it is an array or object.

To store data in localStorage, first of all stringify it using the JSON.stringify() method.

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

Then when you want to retrieve data, you need to parse the string to object again.

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

评论

1赞 Ashish Kamble 9/15/2021
Thanks,i got concept of localstorage cleared
-6赞 manasa woddeyar manu 2/6/2020 #20
localStorage.setItem('user', JSON.stringify(user));

Then to retrieve it from the store and convert to an object again:

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

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

localStorage.clear();

评论

12赞 Kristopher Johnson 2/7/2020
This is a 10-year-old question. Do you think your answer adds anything not already covered by the other answers?
1赞 Kamil Kiełczewski 8/4/2020 #21

Circular References

In this answer I focus on data-only objects (without functions, etc.) with circular references and develop ideas mentioned by maja and mathheadinclouds (I use his test case and my code is several times shorter).

Actually, we can use JSON.stringify with a proper replacer - if the source object contains multi-references to some object, or contains circular references then we reference it by special path-string (similar to 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
// ---------------

// Generate 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 content 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 answer: ');

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

评论

1赞 bdombro 10/3/2023
感谢您对 99% 的用例使用显而易见的方法进行回答,并包括有关循环引用的提示,这对许多人来说是一个巨大的问题。
0赞 Gabriel H 3/19/2021 #22

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

The library handles all the pitfalls such as cyclic reference, attributes aliasing, etc.

Simply describe your class using the @JsonProperty() and @JsonClassType() decorators.

Serialize your object using:

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

For slightly more detailed explanation, check my answer here:

Typescript objects serialization?

And the Jackson-js tutorial here:

Jackson-js: Powerful JavaScript decorators to serialize/deserialize objects into JSON and vice versa (Part 1)

评论

0赞 bdombro 10/3/2023
This answer could be improved by comparing against alternatives approaches.
1赞 Dan Loewenherz 4/5/2021 #23

This question has been answered sufficiently from the JavaScript-only perspective, and others have already noted that both and have no concept of objects—they handle strings and strings only. This answer provides a TypeScript-friendly solution that incorporates what others have suggested in JavaScript-only solutions.localStorage.getItemlocalStorage.setItem

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

Usage

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

Explanation

We declare both and functions on the prototype— is an instance of this type. There's nothing special we really need to note besides the null handling in . Since can return , we must exit early since calling on a value will throw a runtime exception.setObjectgetObjectStoragelocalStoragegetObjectgetItemnullJSON.parsenull

After declaring the functions on the prototype, we include their type definitions on the type in the global namespace.StorageStorage

Note: If we defined these functions with arrow functions, we'd need to assume that the storage object we're calling is always , which might not be true. For instance, the above code will add and support to as well.localStoragesetObjectgetObjectsessionStorage

评论

1赞 Flimm 12/17/2021
It's generally not a good idea to monkey-patch a global shipped by the browser. It can break other code, and it's not future-compatible.
3赞 Connect2sky 12/17/2021 #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