Câu hỏi Lưu trữ các đối tượng trong HTML5 localStorage


Tôi muốn lưu trữ một đối tượng JavaScript trong HTML5 localStorage, nhưng đối tượng của tôi dường như đang được chuyển đổi thành một chuỗi.

Tôi có thể lưu trữ và truy xuất các loại và mảng JavaScript nguyên thủy sử dụng localStorage, nhưng các đối tượng dường như không hoạt động. Họ nên?

Đây là mã của tôi:

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

Đầu ra bàn điều khiển là

typeof testObject: object
testObject properties:
  one: 1
  two: 2
  three: 3
typeof retrievedObject: string
Value of retrievedObject: [object Object]

Nó trông giống như tôi setItem là chuyển đổi đầu vào thành chuỗi trước khi lưu trữ.

Tôi thấy hành vi này trong Safari, Chrome và Firefox, vì vậy tôi cho rằng đó là sự hiểu lầm của tôi về Lưu trữ web HTML5 spec, không phải là lỗi hoặc giới hạn cụ thể cho trình duyệt.

Tôi đã cố gắng hiểu được bản sao có cấu trúc thuật toán được mô tả trong http://www.w3.org/TR/html5/infrastructure.html. Tôi không hoàn toàn hiểu những gì nó nói, nhưng có lẽ vấn đề của tôi đã làm với các thuộc tính của đối tượng của tôi không được liệt kê (???)

Có cách giải quyết dễ dàng không?


Cập nhật: W3C cuối cùng đã thay đổi suy nghĩ của họ về đặc tả cấu trúc bản sao, và quyết định thay đổi spec để phù hợp với việc triển khai. Xem https://www.w3.org/Bugs/Public/show_bug.cgi?id=12111. Vì vậy, câu hỏi này không còn 100% hợp lệ, nhưng câu trả lời vẫn có thể được quan tâm.


2057
2018-01-06 04:05


gốc


BTW, đọc của bạn về "thuật toán nhân bản có cấu trúc" là chính xác, nó chỉ là spec đã được thay đổi từ các giá trị chỉ có chuỗi này sau khi triển khai đã được ra ngoài. Tôi đã gửi lỗi bugzilla.mozilla.org/show_bug.cgi?id=538142 với mozilla để theo dõi vấn đề này. - Nickolay
Điều này có vẻ giống như một công việc cho indexedDB ... - markasoftware
Làm thế nào về việc lưu trữ một mảng các đối tượng trong localStorage? Tôi đang đối mặt với cùng một vấn đề mà nó đang nhận được chuyển đổi thành chuỗi. - Jayant Pareek
bạn có thể thay vì serialize mảng? như lưu trữ với JSON stringify sau đó phân tích cú pháp một lần nữa khi tải? - Brandito
Bạn có thể dùng localDataStorage để lưu trữ một cách minh bạch các loại dữ liệu javascript (Array, Boolean, Date, Float, Integer, String và Object) - Mac


Các câu trả lời:


Hãy nhìn vào táo, Mozilla và Microsoft tài liệu, chức năng dường như bị hạn chế chỉ xử lý các cặp khóa / giá trị chuỗi.

Cách giải quyết có thể là xâu chuỗi đối tượng của bạn trước khi lưu trữ nó, và sau đó phân tích nó khi bạn lấy nó:

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

2654
2018-01-06 04:25



hãy lưu ý rằng mọi siêu dữ liệu sẽ bị xóa. bạn chỉ nhận được một đối tượng với các cặp khóa-giá trị, vì vậy bất kỳ đối tượng nào có hành vi cần phải được xây dựng lại. - oligofren
@CMS có thể setItem ném một số ngoại lệ nếu dữ liệu vượt quá dung lượng? - Ashish Negi
... chỉ áp dụng cho các đối tượng có tham chiếu vòng tròn, JSON.stringify() mở rộng đối tượng được tham chiếu đến "nội dung" đầy đủ của nó (được xâu chuỗi hoàn toàn) trong đối tượng mà chúng ta xâu chuỗi. Xem: stackoverflow.com/a/12659424/2044940 - CoDEmanX
Vấn đề với cách tiếp cận này là các vấn đề về hiệu suất, nếu bạn phải xử lý các mảng hoặc đối tượng lớn. - Monkey King
@oligofren đúng, nhưng như maja đề xuất chính xác eval () =>, đây là một trong những cách sử dụng tốt, bạn có thể dễ dàng lấy lại mã hàm => lưu trữ nó dưới dạng chuỗi và sau đó eval () nó trở lại :) - jave.web


Một cải tiến nhỏ trên một biến thể:

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

Bởi vì đánh giá ngắn mạch, getObject() sẽ ngay trở về null nếu key không có trong Bộ nhớ. Nó cũng sẽ không ném một SyntaxError ngoại lệ nếu value Là "" (chuỗi rỗng; JSON.parse() không thể xử lý điều đó).


563
2018-06-30 06:45



Tôi chỉ muốn thêm nhanh việc sử dụng vì nó không rõ ràng cho tôi: var userObject = { userId: 24, name: 'Jack Bauer' };  Và đặt nólocalStorage.setObject('user', userObject);  Sau đó lấy lại từ bộ nhớuserObject = localStorage.getObject('user');  Bạn thậm chí có thể lưu trữ một mảng các đối tượng nếu bạn muốn. - zuallauz
Nó chỉ là biểu thức boolean. Phần thứ hai chỉ được đánh giá nếu phần còn lại là đúng. Trong trường hợp đó, kết quả của toàn bộ biểu thức sẽ là từ phần bên phải. Đó là kỹ thuật phổ biến dựa trên cách thức các biểu thức boolean được đánh giá. - Guria
Tôi không thấy điểm của biến cục bộ và đánh giá phím tắt ở đây (cải thiện hiệu suất nhỏ sang một bên). Nếu key không có trong Local Storage, window.localStorage.getItem(key) trả về null - nó có không phải ném ngoại lệ "Truy cập trái phép" - và JSON.parse(null) trả về null cũng vậy - nó không phải ném một ngoại lệ, không phải trong Chromium 21 cũng không phải mỗi ES 5.1 phần 15.12.2, bởi vì String(null) === "null" có thể được hiểu là JSON chữ. - PointedEars
Các giá trị trong Local Storage luôn là các giá trị chuỗi nguyên thủy. Vì vậy, những gì đánh dấu phím tắt này không xử lý là khi ai đó được lưu trữ "" (chuỗi rỗng) trước đây. Bởi vì nó chuyển đổi thành false và JSON.parse(""), sẽ ném một SyntaxError ngoại lệ, không được gọi. - PointedEars
Điều này sẽ không hoạt động trong IE8, vì vậy bạn nên sử dụng các hàm trong câu trả lời được xác nhận nếu bạn cần hỗ trợ nó. - Ezeke


Bạn có thể thấy hữu ích khi mở rộng đối tượng Storage bằng các phương thức tiện dụng này:

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

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

Bằng cách này bạn sẽ có được các chức năng mà bạn thực sự muốn mặc dù bên dưới API chỉ hỗ trợ chuỗi.


196
2018-01-06 04:42



Việc đưa CMS vào một hàm là một ý tưởng hay, nó chỉ cần một thử nghiệm tính năng: Một cho JSON.stringify, một cho JSON.parse và một để kiểm tra xem localStorage có thể được đặt trong thực tế và lấy một đối tượng hay không. Sửa đổi đối tượng lưu trữ không phải là một ý tưởng hay; Tôi thà thấy điều này như một phương pháp riêng biệt và không phải là localStorage.setObject. - Garrett
Điều này getObject() sẽ ném một SyntaxError ngoại lệ nếu giá trị được lưu trữ là "", bởi vì JSON.parse() không thể xử lý điều đó. Xem bản chỉnh sửa của tôi cho câu trả lời của Guria để biết chi tiết. - PointedEars
Chỉ cần hai xu của tôi, nhưng tôi khá chắc chắn nó không phải là một ý tưởng tốt để mở rộng các đối tượng được cung cấp bởi các nhà cung cấp như thế này. - Sethen


Mở rộng đối tượng Storage là một giải pháp tuyệt vời. Đối với API của tôi, tôi đã tạo ra một mặt tiền cho localStorage và sau đó kiểm tra xem nó có phải là một đối tượng hay không trong khi thiết lập và nhận được.

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

64
2018-01-21 18:29



Đây gần như chính xác những gì tôi cần. Chỉ cần thêm if (value == null) {return false} trước khi bình luận, nếu không nó sẽ dẫn đến lỗi khi kiểm tra sự tồn tại của một khóa trên localStorage. - Francesco Frapporti
Điều này là khá mát mẻ thực sự. Đồng ý với @FrancescoFrapporti bạn cần có nếu có giá trị null. Tôi cũng đã thêm một '|| giá trị [0] == "[" 'kiểm tra trong trường hợp có trong một mảng trong đó. - rob_james
Tốt, tôi sẽ chỉnh sửa nó. Mặc dù bạn không cần phần rỗng, nhưng nếu bạn làm tôi đề nghị ba ===. Nếu bạn sử dụng JSHint hoặc JSLint, bạn sẽ được cảnh báo về việc sử dụng ==. - Alex Grande
Và đối với những người không phải ninja (như tôi), ai đó có thể vui lòng cung cấp một ví dụ sử dụng cho câu trả lời này không? Là nó: data.set('username': 'ifedi', 'fullname': { firstname: 'Ifedi', lastname: 'Okonkwo'});? - Ifedi Okonkwo
Đúng vậy! Khi tôi vượt qua mong muốn của tôi để được muỗng ăn, tôi lấy mã để kiểm tra ra, và đã nhận nó. Tôi nghĩ câu trả lời này rất tuyệt vì 1) Không giống như câu trả lời được chấp nhận, cần có thời gian để kiểm tra một số dữ liệu chuỗi và 2) Không giống như phần tiếp theo, nó không mở rộng đối tượng gốc. - Ifedi Okonkwo


Có một thư viện tuyệt vời bao gồm nhiều giải pháp để nó thậm chí hỗ trợ các trình duyệt cũ hơn được gọi là jStorage

Bạn có thể đặt một đối tượng

$.jStorage.set(key, value)

Và lấy nó dễ dàng

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

52
2017-08-23 03:52



$ là bất hợp pháp !!! - SuperUberDuper
@SuperUberDuper jStorage yêu cầu Prototype, MooTools hoặc jQuery - JProgrammer


Stringify không giải quyết được mọi vấn đề

Dường như các câu trả lời ở đây không bao gồm tất cả các loại có thể có trong JavaScript, do đó, đây là một số ví dụ ngắn về cách xử lý chúng một cách chính xác:

//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"

Tôi không khuyên bạn để lưu trữ các hàm bởi vì eval() là điều ác có thể dẫn đến các vấn đề liên quan đến bảo mật, tối ưu hóa và gỡ lỗi.         Nói chung, eval() không bao giờ nên được sử dụng trong mã JavaScript.

Thành viên riêng tư

Vấn đề với việc sử dụng JSON.stringify()để lưu trữ các đối tượng là, chức năng này không thể nối tiếp các thành viên riêng. Vấn đề này có thể được giải quyết bằng cách ghi đè lên .toString() phương thức (được gọi ngầm khi lưu trữ dữ liệu trong lưu trữ web):

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

Tham chiếu thông tư

Một vấn đề khác stringify không thể đối phó với các tham chiếu vòng tròn:

var obj = {};
obj["circular"] = obj;
localStorage.object = JSON.stringify(obj);  //Fails

Trong ví dụ này, JSON.stringify() sẽ ném một TypeError  "Chuyển đổi cấu trúc tròn thành JSON".         Nếu lưu trữ tham chiếu vòng tròn nên được hỗ trợ, tham số thứ hai của JSON.stringify() có thể được sử dụng:

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

Tuy nhiên, việc tìm kiếm một giải pháp hiệu quả để lưu trữ các tham chiếu vòng tròn phụ thuộc rất lớn vào các nhiệm vụ cần được giải quyết và việc khôi phục dữ liệu đó cũng không đáng kể.

Đã có một số câu hỏi về SO đối phó với vấn đề này: Stringify (chuyển đổi thành JSON) một đối tượng JavaScript với tham chiếu vòng tròn


50
2017-11-19 09:51





Sử dụng các đối tượng JSON để lưu trữ cục bộ:

//BỘ

var m={name:'Hero',Title:'developer'};
localStorage.setItem('us', JSON.stringify(m));

//ĐƯỢC

var gm =JSON.parse(localStorage.getItem('us'));
console.log(gm.name);

// Lặp lại tất cả các giá trị và khóa lưu trữ cục bộ

for (var i = 0, len = localStorage.length; i < len; ++i) {
  console.log(localStorage.getItem(localStorage.key(i)));
}

// XÓA BỎ

localStorage.removeItem('us');
delete window.localStorage["us"];

29
2017-11-20 07:06





Về lý thuyết, có thể lưu trữ các đối tượng với các hàm:

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

Tuy nhiên, chức năng tuần tự hóa / deserialization là không đáng tin cậy bởi vì nó phụ thuộc vào việc thực thi.


27
2018-04-05 21:20



Chức năng tuần tự hóa / deserialization là không đáng tin cậy bởi vì nó phụ thuộc vào việc thực thi. Ngoài ra, bạn muốn thay thế c.f[k] = escape(a[k]);  với Unicode an toàn c.f[k] = encodeURIComponent(a[k]); và eval('b.' + k + ' = ' + unescape(data.f[k])); với b[k] = eval("(" + decodeURIComponent(data.f[k]) + ")");. Các dấu ngoặc đơn được yêu cầu bởi vì hàm của bạn, nếu được tuần tự hóa đúng, có thể là ẩn danh, mà không phải là-là hợp lệ / Statement / (như vậy eval()) sẽ ném một SyntaxError ngoại lệ khác). - PointedEars
Và typeof là một nhà điều hành, không viết nó như thể nó là một chức năng. Thay thế typeof(a[k]) với typeof a[k]. - PointedEars
Ngoài việc áp dụng các đề xuất của tôi và nhấn mạnh sự không đáng tin cậy của phương pháp tiếp cận, tôi đã sửa các lỗi sau: 1. Không phải tất cả các biến đều được khai báo. 2. for- -in không được lọc cho các thuộc tính riêng. 3. Kiểu mã, bao gồm cả tham chiếu, không phù hợp. - PointedEars
@PointedEars sự khác biệt thực tế này thực hiện điều gì? spec nói the use and placement of white space, line terminators, and semicolons within the representation String is implementation-dependent.  Tôi không thấy bất kỳ sự khác biệt chức năng nào. - Michael
@Michael Phần mà bạn trích dẫn bắt đầu bằng Note *in particular* that …. Nhưng đặc tả giá trị trả lại bắt đầu bằng An implementation-dependent representation of the function is returned. This representation has the syntax of a FunctionDeclaration. Giá trị trả về có thể là function foo () {} - giả sử một phù hợp thực hiện. - PointedEars