Когда появился 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), и десериализуется при старте приложения.