Когда появился localStorage я был рад, как слон, но, немногим позже, после подробного изучения вопроса, я несколько разочаровался: там могут храниться только строки, а об объектах можно было забыть. Приходилось превращать их в строки для хранения, приходилось обратно конвертировать строку в объект для работы с ним, затем опять конвертировать в строку, чтоб сохранить. Не спорю, есть замечательные библиотеки, позволяющие манипулировать хранилищем максимально просто, но вызывать функции как-то не очень хотелось.
Круто было бы, не так ли?
Относительно давно, где-то около полугода назад я задался вопросом: как же, блин, сделать так, чтоб можно было работать с localStorage вообще без функций, чисто как с объектом. Challenge accepted!
Как говорит небезызвестный Геша: “Всё, я здзелал”.
Дальше читать не обязательно. Более разумное решение находится здесь: habrahabr.ru/post/144998
Главной подзадачей было изобретение способа повесить сеттер не только на сам объект, но и на подобъекты, причем ключи заранее не известны. Это решается крайне просто: ставим геттер на объект:
Теперь, когда мы присваиваем что-либо какому-либо из ключей,
Вызывается геттер.
Итак, всё до безобразия просто.
(Прошу обратить внимание, что таймер вызывается только для сохранения результатов в localStorage. В остальном, результат присваивания предсказуем, как и в обычном объекте, так как _objectLocalStorage возвращается сразу. Навеяно этим комментарием.)
Вставляем код выше в свой js файл, и используем:
Перезагружаем страницу,
При гете objectLocalStorage возвращается локальный объект _objectLocalStorage. Значит, когда мы присваиваем что-нибудь одному из ключей подобъекта в objectLocalStorage, возвращается _objectLocalStorage и присваивание идет в него. То есть
Но, в первом случае, после присваивания, вызывается сохранение объекта в localStorage. Если не поместить эту часть в таймаут, то сохранение в хранилище будет до присваивания. Согласен, такой способ чреват багами, но другого способа сохранять после, я не нашел.
Вот, собственно, и всё. В идеале, конечно, хорошо бы иметь:
Но это потом, я просто хотел поделиться радостью :)
Лучей бобра вам.
UPD
Хотел бы отдельно выделить альтернативное, простое и гениальное решение, высказанное Scalar в комментариях.
localStorage = { a: { b: 1 }, c: { d: 2 } } localStorage.a.b = 3;
Круто было бы, не так ли?
Относительно давно, где-то около полугода назад я задался вопросом: как же, блин, сделать так, чтоб можно было работать с localStorage вообще без функций, чисто как с объектом. Challenge accepted!
Как говорит небезызвестный Геша: “Всё, я здзелал”.
Дальше читать не обязательно. Более разумное решение находится здесь: habrahabr.ru/post/144998
Главной подзадачей было изобретение способа повесить сеттер не только на сам объект, но и на подобъекты, причем ключи заранее не известны. Это решается крайне просто: ставим геттер на объект:
Object.defineProperty( window, 'objectLocalStorage', { // возвращается какой-то объект get: function() { return {}; } });
Теперь, когда мы присваиваем что-либо какому-либо из ключей,
window.objectLocalStorage.a = 1;
Вызывается геттер.
Итак, всё до безобразия просто.
(function() { // объект, который будет хранить данные, пока окно браузера не перезагрузят // берем данные из хранилища в виде json и парсим их var _objectLocalStorage = JSON.parse( localStorage.getItem( 'objectStorage' ) ) || {}, timer = null; // определяем объект с именем objectLocalStorage в window и добавляем ему геттер и сеттер // во избежание недоразумений, мы не трогаем localStorage, он каким был, тактим остаётся Object.defineProperty( window, 'objectLocalStorage', { get: function() { // timer нужен для того, чтоб не вызывать стрингификацию при каждом запросе объекта // и для того, чтоб старые данные localStorage не переписали новые данные, // что было следствием асинхронности // setTimeout для сохранения объекта >после< присваивания, а не до if( timer === null ) { timer = setTimeout( function(){ var stringified = JSON.stringify( _objectLocalStorage ); // некое подобие оптимизации: если данные в объекте не изменились, // значит присваивания никакого не было, сработал обычный гет if( stringified !== localStorage.getItem( 'objectStorage' ) ) { // сохраняем localStorage.setItem( 'objectStorage', stringified ); } timer = null; }, 0); } return _objectLocalStorage; }, // на случай, если objectLocalStorage присвоили целый объект set: function( v ) { _objectLocalStorage = v; localStorage.setItem( 'objectStorage', JSON.stringify( _objectLocalStorage ) ); } } ); })();
(Прошу обратить внимание, что таймер вызывается только для сохранения результатов в localStorage. В остальном, результат присваивания предсказуем, как и в обычном объекте, так как _objectLocalStorage возвращается сразу. Навеяно этим комментарием.)
Как пользоваться?
Вставляем код выше в свой js файл, и используем:
objectLocalStorage = { a: 4, b: {c: 2} }; objectLocalStorage.b.c = {d: 5}
Перезагружаем страницу,
console.log( objectLocalStorage ); // { a: 4, b: {c: {d: 5}} }
Как это работает?
При гете objectLocalStorage возвращается локальный объект _objectLocalStorage. Значит, когда мы присваиваем что-нибудь одному из ключей подобъекта в objectLocalStorage, возвращается _objectLocalStorage и присваивание идет в него. То есть
аналогичноobjectLocalStorage.a.b = 5;
_objectLocalStorage.a.b = 5;
Но, в первом случае, после присваивания, вызывается сохранение объекта в localStorage. Если не поместить эту часть в таймаут, то сохранение в хранилище будет до присваивания. Согласен, такой способ чреват багами, но другого способа сохранять после, я не нашел.
Вот, собственно, и всё. В идеале, конечно, хорошо бы иметь:
- Поддержку IE < 9. Как известно, Object.defineProperty не кроссбраузерен
- Навешивание геттера на все дочерние объекты, то есть, сейчас:
objectLocalStorage.a.b = 5; // сработает a = objectLocalStorage.a; a.b = 5; // не сработает
- Сделать то же самое, но для sessionStorage
Но это потом, я просто хотел поделиться радостью :)
Лучей бобра вам.
UPD
Хотел бы отдельно выделить альтернативное, простое и гениальное решение, высказанное Scalar в комментариях.
Мне кажется проще читать и писать в обычный объект, который сериализуется в LS (по таймауту и при onbeforeunload), и десериализуется при старте приложения.
