Работа с локальным хранилищем, как с объектом? Легко!

в 17:08, , рубрики: javascript, localStorage, sessionstorage, storage, Веб-разработка, метки: , ,

Когда появился localStorage я был рад, как слон, но, немногим позже, после подробного изучения вопроса, я несколько разочаровался: там могут храниться только строки, а об объектах можно было забыть. Приходилось превращать их в строки для хранения, приходилось обратно конвертировать строку в объект для работы с ним, затем опять конвертировать в строку, чтоб сохранить. Не спорю, есть замечательные библиотеки, позволяющие манипулировать хранилищем максимально просто, но вызывать функции как-то не очень хотелось.

localStorage = { a: {b: 1}, c: { d:2 }}
localStorage.a.b = 3;

Круто было бы, не так ли?

Относительно давно, где-то около полугода назад я задался вопросом: как же, блин, сделать так, чтоб можно было работать с localStorage вообще без функций, чисто как с объектом. Challenge accepted!

Как говорит небезызвестный Геша: “Всё, я здзелал”.

Главной подзадачей было изобретение способа повесить сеттер не только на сам объект, но и на подобъекты, причем ключи заранее не известны. Это решается крайне просто: ставим геттер на объект:

window.objectLocalStorage = {};
Object.defineProperty( window, 'objectLocalStorage', {
		get: function() {...}
});

Теперь, когда мы присваиваем что-либо какому-либо из ключей,

window.objectLocalStorage.a = 1;

Вызывается геттер.

Итак, всё до безобразия просто.

( function() {
	// объект, который будет хранить данные, пока окно браузера не перезагрузят
	// берем данные из хранилища в виде json и парсим их
	var _objectLocalStorage = JSON.parse( localStorage.getItem( 'objectStorage' ) ) || {};
	// определяем объект с именем objectLocalStorage в window и добавляем ему геттер и сеттер
	// во избежание недоразумений, мы не трогаем localStorage, он каким был, тактим остаётся
	Object.defineProperty( window, 'objectLocalStorage', {
		get: function() {
			// для сохранения объекта после присваивания
			setTimeout( function(){
				var stringified = JSON.stringify( _objectLocalStorage );
				// некое подобие оптимизации: если данные в объекте не изменились,
				// значит присваивания никакого не было, сработал обычный гет
				if( stringified !== localStorage.getItem( 'objectStorage' ) ) {
					// сохраняем 
					localStorage.setItem( 'objectStorage', stringified) );
				}
			}, 0);
			
			return _objectLocalStorage;
		},
		// на случай, если objectLocalStorage присвоили целый объект
		set: function( v ) {
			_objectLocalStorage = v;
			localStorage.setItem( 'objectStorage', JSON.stringify( _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

Но это потом, я просто хотел поделиться радостью :)

Лучей бобра вам.

Автор: Finom

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js