«Виртуальное хранилище» на стороне клиента с jQuery

В некоторых случаях абсолютно бессмысленно и неоправданно нагружать как клиентскую часть веб-приложения, так и серверную. Чтобы не ходить долго вокруг да около, приведу пример из жизни. От разработчика мне достался один интернет-магазин, в котором работа с корзиной происходила следующим образом. При клике на кнопку добавления товара в cookie сохранялись ID товара и его количество. Соответственно, чтобы при посещении различных страниц пользователю показывалось, что находится в корзине, в каком количестве, сколько это добро стоит и прочие данные, серверному приложению приходилось выполнять следующие функции:
  1. получение списка ID товаров из cookie;
  2. запрос в БД, из которой возвращалось название товара, его стоимость и прочие необходимые данные;
  3. использование шаблонизатора (Smarty) для генерирование блока корзины на ряду с генерацией остального содержимого.

Все вроде бы и ничего. Я думаю, многие с подобными схемами сталкивались и не раз. Но передо мной стояла задача оптимизации приложения, и я решил убрать среди прочего лишнюю нагрузку с сервера путем устранения как запросов в БД, так и генерацией блока корзины. Хотелось бы хранить все данные о выбранных товарах на стороне клиента. Причем, в идеале хотелось хранить не только массив выбранных товаров, но и уже готовый HTML-код блока корзины, кроме того, таблицу с товарами для страницы оформления заказа. Но как это сделать?

Ждать кроссбраузерной поддержки JavaScript с клиентским хранилищем можно еще очень долго. А использовать cookie для этой задачи нецелесообразно, как минимум потому, что длина строки в cookie весьма ограничена. Я предлагаю следующее решение в виде плагина для jQuery и небольшого дополнения к серверной части.

JavaScript


;(function($){
	var self={
		config:		{
			//Вызов функции после того, как все объекты будут считаны из хранилища
			callback:	function(){},
			//Путь к серверному хранилищу
			path:		'storage.php'
		},
		current:	{
			//Вызвано впервые?
			first:		true
		},
		//Само хранилище данных
		storage:	{},
		init:		function(objects,config){
			if(!self.current.first)return;
			self.current.first=false;
			//Уст. конфиг
			$.extend(self.config,config);
			//Обраб. объекты
			self.storage=objects;
			//Восст. объекты
			self.get();
			$(window).unload(self.set);
			$('iframe').unload(self.set);
		},
		//Расширяет объекты
		ext:		function(objects){
			for(var k in objects){
				if(typeof(self.storage[k])=='undefined'){
					self.storage[k]={};
				};
				//Расширяем
				if(typeof(objects[k])=='object'){
					$.extend(true,self.storage[k],objects[k]);
				} else {
					self.storage[k]=objects[k];
				};
			};
		},
		//Возвращает объекты из хранилища
		get:		function(){
			$.getJSON(self.config.path,function(data){
				self.ext(data);
				self.config.callback();
			});
		},
		//Сохраняет в хранилище
		set:		function(){
			$.ajax({
				type:	'POST',
				url:	self.config.path,
				data:	{
					storage:	JSON.stringify(self.storage)
				}
			});
		}
	};
	$.extend({
		storage:	self.init,
		storageUpdate:	self.set
	});
})(jQuery);


PHP: storage.php


<?
session_start();
if(isset($_POST['storage'])){
	$_SESSION['storage']=$_POST['storage'];
}
elseif(isset($_SESSION['storage'])){
	echo $_SESSION['storage'];
}
?>


Таким образом, мы имеем виртуальное хранилище любых данных на стороне клиента с косвенным использованием сервера. Как это можно применить на практике?

var a={i:0};
$(function(){
	$.storage({storageA:a},{
		callback:	function(){
			a.i++;
			alert('Вы загрузили страницу в '+a.i+'-й раз.');
		}
	});
});


То есть для разработчика приложения схема хранения данных абсолютно прозрачна, и ему не стоит беспокоиться о том, в каком виде и как хранить данные. В данном случае все данные объекта a перед переходом на другую страницу будут сохранены в нашем хранилище. И сразу же после открытия другой страницы данные будут занесены в объект a.

P. S. Многие могу воскликнуть, мол, зачем такая сложность, если можно оптимизировать серверную часть приложения, хранить данные сразу в сессии и отдавать тот же самый объект клиенту сразу при генерации страницы (тут вариантов масса). У меня есть ответ. Давайте представим, что мы пошли по пути оптимизации приложения, где почти все динамические страницы хранятся во временном серверном кэше в статическом HTML-виде. Кстати, если необходимо таким образом хранить более важные данные, эту схему можно «подсолить» хэш-защитой с помощью cookie.
Поделиться публикацией

Комментарии 24

    +1
    jStore для вас :) code.google.com/p/jquery-jstore/
      0
      Спасибо, подобного комментария я и ожидал. Хороший плагин. Но относительно тяжелый. Удивлен, что он не предлагает использовать механизм сессии сервера.

      К сожалению, из всех предложенных автором вариантов хранилищ, пожалуй, можно остановиться на новой спецификации HTML — 5. От варианта с флешем я давно отказался, так как он не поддерживается, например, активно распространяющимися iPhone и iPad.
        0
        а зачем, если он универсальный?
          0
          В смысле, хоть какой-то способ, да прокатит? Возможно и так. Попробуем его задействовать на практике.
      +2
        0
        Идея данного решения ко мне пришла определенное время назад. Да, несомненно, HTML5 предлагает мощные решения. Но, увы, на данный момент еще есть браузеры, которые это понимают плохо или не понимают вообще :).
      0
      И что сильно разгрузили сервер?
      А как быть если изменится наименование товара или цена?
        0
        Если вы имеете в виду статический кэш страниц, то это, конечно, очень помогло. После внесения изменений в базу можно принудительно запустить обновление всего кэша в фоновом режиме.
          –1
          > Если вы имеете в виду статический кэш страниц, то это, конечно, очень помогло.
          А конкретно цифры можете назвать?
            0
            Тестировал тут loadimpact.com. Для 30 клиентов время с 25с упало до 2с. Этих значений вам достаточно?
        0
        все перечисленные проблемы:
        получение списка ID товаров из cookie;
        запрос в БД, из которой возвращалось название товара, его стоимость и прочие необходимые данные;
        использование шаблонизатора (Smarty) для генерирование блока корзины на ряду с генерацией остального содержимого.

        прекрасно решаются простейшим кэшированием HTML всего блока на стороне сервера по ключу, равному этой самой cookie.
        даже сессии не нужны, обычный apc/memcache или что там на его месте.

        если хочется оболочку жестко закэшировать — можно в процессе генерации кэша корзины выносить его содержимое в какой-нибудь "cache/" . md5($cart_cookie) . ".js" и его грузить на клиенте (адрес жаваскриптом вычислять по той же куке)
        будет замечательный статичный файл вообще без всякого php, только периодически надо убивать старые файлы.

        иначе никакого смысла в оптимизации оболочки нет — у вас все равно на каждой странице запрос к php.

        можно пойти еще дальше и даже добавление/удаление в корзину делать чисто на клиенте, исправлением куки прямо из js; но тогда надо на cache/*.js еще обработчик 404 повесить, чтобы он перегенерировал кэш для нового значения куки.
          0
          Вы не находите, что этот вариант более сложный, чем тот, который предлагаю я (и еще более сложный, чем то же HTML5-хранилище на стороне клиента)? Как минимум потому, что у вас кэш будет засоряться временными, по сути, ненужными файлами.
          иначе никакого смысла в оптимизации оболочки нет — у вас все равно на каждой странице запрос к php.
          Очень даже есть, так как у меня не грузится громоздкое серверное приложение, а всего лишь скрипт из неск. строк. А memcache можно попробовать использовать, спасибо, но я думаю, особого выигрыша не будет для объема данных в пару Кб (могу и ошибаться).
            0
            у чисто клиентского хранилища есть большой недостаток — его никак не почистить с сервера, если данные вдруг устареют.
            если это не критично, то конечно такой вариант лучше, т.к. вообще никаких запросов не делается.

            но раз уж все равно дергать php каждый раз, всегда можно обойтись одним запросом вместо двух — например, взять общий для всех закэшированный код оболочки и подставить туда данные из сессии.
            (хотя бы просто в конец дописать через <script>)

            до громоздкого приложения запрос в любом случае не долетит, зато клиенту не надо будет делать еще один запрос, который начинает выполняться только после полной загрузки оболочки.
            серверу все равно, а скорость загрузки для клиента будет заметно выше.
              0
              у чисто клиентского хранилища есть большой недостаток — его никак не почистить с сервера, если данные вдруг устареют.
              Согласен, но я привел пример, где данные от сервера совершенно никак не зависят. Сервер тут не причем.
              до громоздкого приложения запрос в любом случае не долетит, зато клиенту не надо будет делать еще один запрос, который начинает выполняться только после полной загрузки оболочки.
              А клиент и так делает всего один запрос — именно к storage.php, так как все остальное, в том числе и сама страница, уже находится в кэше браузера. Кстати, никто не мешает делать асинхронный запрос к хранилищу не после всей загрузки страницы, а лишь после загрузки необходимых JS-библиотек.
                0
                Кстати, в заголовке сразу можно подгружать text/javascript, обращаясь к storage.php, который будет выводить не строку JSON, а уже var storage={...} в готовом виде. Тогда точно можно не ждать загрузки всей страницы :)
                  0
                  так у вас «один интернет-магазин» или полноценное full-ajax приложение?
                  если все приложение можно запихнуть в одну страницу и намертво ее закэшировать на клиенте это одно.
                  если же это более традиционный сайт с массой статичных страниц с динамическим блоком на каждой — все их в кэш пользователю не засунуть, получаем при переходах по сайту те самые 2 запроса на страницу.
                    0
                    Я думаю — это не та проблема, на которой стоит акцентировать внимание. Походите по любому, как вы сказали, более традиционному сайту, и посмотрите сколько запросов на страницу клиент отправляет к серверу. Изображения, я думаю, перевесят то, что мы обсуждаем.
                      0
                      речь шла о первоначальном варианте с аякс запросом по загрузке, который не параллелится с загрузкой остальной статики.
                      в варианте со <script src=«storage.php»> все уже не так плохо.

                      но от лишних запросов к php все же есть смысл избавляться вовсе, если есть возможность это делать (см. первый коммент), сравните, сколько раз в секунду сервер может отдать статичный файл, а сколько — даже пустую страницу, сгенерированную php.
                        0
                        Вообще, изначально речь шла о хранилище данных на стороне клиента, пусть даже и виртуальном. А способов реализовать сие масса. Тут надо смотреть на сам проект и делать выбор.
            0
            window.name
              0
              window.name — это имя окна. Какое отношение это имеет к хранилищу?
                0
                Тоже хранилище

                Выполни в строке URL бразера
                javascript:(function(){window.name='the html fragment';}())

                Перегрузи страницу
                Выполни в строке URL бразера
                javascript:(function(){alert(window.name)}())

                Не могу найти на хабре описание.
                В window.name помещается 2 мегабайта данных
                  0
                  Да, решение имеет право на существование, интересно. Но я бы выделил 2 минуса. Во-первых, сомнительно, что может быть 2Мб данных сохранено в window.name. Если это не так — тем лучше. Во-вторых, если пользователь откроет несколько вкладок, эти данных из window.name невозможно будет извлечь из любой другой вкладки (читай, окна).

            Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

            Самое читаемое