ChatGPT解决这个技术问题 Extra ChatGPT

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

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

有简单的解决方法吗?

更新:W3C 最终改变了对结构化克隆规范的看法,并决定更改规范以匹配实现。请参阅 12111 – spec for Storage object getItem(key) method does not match implementation behavior。所以这个问题不再是 100% 有效,但答案仍然可能是有趣的。

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

C
Cœur

查看 AppleMozillaMozilla 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() 将引用的对象扩展为我们字符串化对象中的完整“内容”(隐式字符串化)。请参阅:stackoverflow.com/a/12659424/2044940
如果您必须处理大型数组或对象,这种方法的问题是性能问题。
@oligofren 是的,但正如maja 正确建议的那样 eval() => ,这是 eval() => 的好用处之一,您可以轻松检索函数代码 => 将其存储为字符串,然后 eval() 将其返回 :)
D
Dave Jarvis

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——它确实not 也不会抛出异常,无论是在 Chromium 21 中还是根据 ES 5.1 section 15.12.2,因为 String(null) === "null" 可以解释为 JSON literal
本地存储中的值始终是原始字符串值。所以这个快捷评估确实处理的是某人之前存储了 "" (空字符串)。因为它类型转换为 false 并且 JSON.parse("") 会引发 SyntaxError 异常,所以不会被调用。
这在 IE8 中不起作用,因此如果您需要支持它,最好使用已确认答案中的功能。
J
Justin Voskuhl

您可能会发现使用这些方便的方法扩展 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 方法的浏览器不兼容。
P
Peter Mortensen

为 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 上是否存在键时会导致错误。
这实际上很酷。同意@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”值。
P
Peter Mortensen

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

但是,找到存储循环引用的有效解决方案在很大程度上取决于需要解决的任务,并且恢复此类数据也并非易事。

Stack Overflow 上已经有一些问题处理这个问题:Stringify (convert to JSON) a JavaScript object with circular reference


因此,不用说 - 将数据存储到 Storage 应该基于简单数据副本的唯一前提。不是活的对象。
这些天可能会使用自定义 toJSON 而不是 toString() 。不幸的是,没有用于解析的对称等价物。
toJSON 不支持没有直接 json 表示的类型,如日期、正则表达式、函数和许多其他在我写完这个答案后添加到 JavaScript 中的新类型。
为什么在 localStorage.num (num = +localStorage.num) 前面加“+”?
@PeterMortensen 将存储的字符串转换回数字
M
Matthew

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

你可以设置一个对象

$.jStorage.set(key, value)

并轻松检索

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

@SuperUberDuper jStorage 需要 Prototype、MooTools 或 jQuery
A
Andy Lorenz

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

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

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 () {}——假设是 conforming 实现。
M
Mac

建议对这里讨论的许多特性使用抽象库,以及更好的兼容性。有很多选择:

jStorage 或 simpleStorage ← 我的偏好

本地饲料

阿列克谢库利科夫/存储

草坪椅

Store.js ← 另一个不错的选择

我的天啊

本地数据存储


P
Peter Mortensen

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

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

这就是为什么您应该将数据转换为字符串,无论它是数组还是对象。

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

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

然后当你想检索数据时,你需要再次将字符串解析为对象。

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

谢谢,我清除了本地存储的概念
P
Peter Mortensen

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

[免责声明] 我是该实用程序的作者 [/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,它们可以工作,但它比能够更麻烦只需使用像这样的实用程序。我要试试。
T
Tony Brix

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

EJSON 是 JSON 的扩展,支持更多类型。它支持所有 JSON 安全类型,以及: Date (JavaScript Date) Binary (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

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


m
mar10

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

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

https://i.stack.imgur.com/cU5V0.png

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


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

对于愿意设置和获取类型化属性的 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");

m
maja

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


F
Flimm
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

z
zevero

我制作了另一个只有 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


R
Rudie

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

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 现在在所有主要浏览器中都已弃用。
m
mathheadinclouds

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

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

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

https://i.stack.imgur.com/4taQx.png

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< /上一个>


d
dwlz

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

打字稿 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 中的 null 处理之外,没有什么特别需要注意的。由于 getItem 可以返回 null,我们必须提前退出,因为对 null 值调用 JSON.parse 会引发运行时异常。

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

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


对浏览器提供的全局补丁进行猴子补丁通常不是一个好主意。它可以破坏其他代码,并且不兼容未来。
P
Peter Mortensen

这是代码 posted by 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; 不起作用。


像这样对全局变量进行猴子补丁通常不是一个好主意。它可能会破坏代码,并且与未来不兼容。
danott 的回答现已被删除。它在 2019 年底被主持人大规模删除答案时被删除,没有任何解释。
P
Peter Mortensen

循环引用

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

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

// JSON.strigify 使用 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);让 isComplex = value === Object(value) if (isComplex) m.set(value, p);让 pp = v.get(值)||'';让路径 = p.replace(/undefined\.\.?/, '');让 val = pp ? `#REF:${pp[0] == '[' ? '$':'$.'}${pp}` : 值; !在里面 ? (init=value) : (val===init ? val="#REF:$" : 0); if(!pp && isComplex) v.set(value, path);返回值; } } // --------------- // 测试 // --------------- // 生成带有重复/循环引用的 obj let obj = { L: { L: { v: 'lorem' }, R: { v: 'ipsum' } }, R: { L: { v: 'dolor' }, R: { L: { v: 'sit' }, R: { v: 'amet' } } } } obj.RLuncle = obj.L; obj.RRuncle = obj.L; obj.RRLuncle = obj.RL; obj.RRRuncle = obj.RL; obj.LLuncle = obj.R; obj.LRuncle = obj.R;测试对象 = 对象;让 json = JSON.stringify(testObject, refReplacer(), 4); console.log("测试对象\n", testObject); console.log("带有 JSONpath 引用的 JSON\n", json);

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

// 使用对象的 JSONpath 引用解析 JSON 内容 function parseRefJSON(json) { let objToPath = new Map();让 pathToObj = new Map();让 o = JSON.parse(json);让遍历=(父,字段)=> {让obj=父;让路径 = '#REF:$'; if (field !== undefined) { obj = parent[field]; path = objToPath.get(parent) + (Array.isArray(parent) ? `[${field}]` : `${field ? '.' + field : ''}`); } objToPath.set(obj, path); pathToObj.set(路径,obj);让 ref = pathToObj.get(obj);如果(参考)父母[字段] = 参考; for (let f in obj) if (obj === Object(obj)) traverse(obj, f); } 遍历(o);返回 o; } // --------------- // 测试 1 // --------------- let json = ` { "L": { " L“:{“v”:“lorem”,“叔叔”:{“L”:{“v”:“dolor”,“叔叔”:“#REF:$.L”},“R”:{“ L”:{“v”:“坐”,“叔叔”:“#REF:$.LLuncle.L”},“R”:{“v”:“amet”,“叔叔”:“#REF:$ .LLuncle.L" }, "uncle": "#REF:$.L" } } }, "R": { "v": "ipsum", "uncle": "#REF:$.LLuncle" } } , "R": "#REF:$.LLuncle" }`;让 testObject = parseRefJSON(json); console.log("测试对象\n", testObject); // --------------- // TEST 2 // --------------- console.log('Tests from mathheadinclouds answer: ') ;让恢复 = testObject; let obj = { // 原始对象 L: { L: { v: 'lorem' }, R: { v: 'ipsum' } }, R: { L: { v: 'dolor' }, R: { L: { v: '坐' }, R: { v: 'amet' } } } } obj.RLuncle = obj.L; obj.RRuncle = obj.L; obj.RRLuncle = obj.RL; obj.RRRuncle = obj.RL; obj.LLuncle = obj.R; obj.LRuncle = obj.R; [ obj.LLv === 恢复.LLv, obj.LRv === 恢复.LRv, obj.RLv === 恢复.RLv, obj.RRLv === 恢复.RRLv, obj.RRRv === 恢复.RRRv , obj.RLuncle === obj.L, obj.RRuncle === obj.L, obj.RRLuncle === obj.RL, obj.RRRuncle === obj.RL, obj.LLuncle === obj.R ,obj.LRuncle === obj.R,recovered.RLuncle ===recovered.L,recovered.RRuncle ===recovered.L,recovered.RRLuncle ===recovered.RL,recovered.RRRuncle ===recovered.RL , 恢复.LLuncle === 恢复.R, 恢复.LRuncle === 恢复.R ].forEach(x => console.log('test pass: ' + x));

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

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

P
Peter Mortensen

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

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

使用 @JsonProperty() 和 @JsonClassType() 装饰器简单地描述你的类。

使用以下方法序列化您的对象:

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

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

Typescript objects serialization?

Jackson-js 教程在这里:

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


S
Samsul Islam
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 年历史的问题。您认为您的答案是否添加了其他答案尚未涵盖的任何内容?