Статьи

Хранилище браузера для приложений HTML5

В течение многих лет одним из главных преимуществ написания настольных приложений был легкий доступ к локальному хранилищу на клиентском компьютере. Веб-разработчики смирились с невозможностью хранить и извлекать данные с компьютера клиента в течение длительного времени, но, похоже, это может скоро измениться. Вы можете даже подумать, что это уже есть после прочтения этой статьи. Да, я собираюсь обсудить происхождение постоянных данных на клиентском компьютере, а затем познакомить вас со стандартом веб-хранилища.

Большинство веб-разработчиков знают, что единственное локальное хранилище, которое мы можем ожидать от веб-браузера, — это файлы cookie. Ну, не совсем. Желание хранить данные на клиентском компьютере не является новой концепцией и не было задумано при создании спецификаций HTML5. Еще более удивительным является то, что рабочая реализация была разработана Microsoft как часть набора функций IE6. Они назвали его userData, и это, по сути, гарантировало не менее 640 КБ локального пространства на домен в зависимости от политик безопасности IE, установленных пользователем. По сегодняшним меркам это может показаться очень небольшим пространством, но если мы сравним его с максимальным объемом в 4 КБ, доступным для нас с помощью файлов cookie, улучшение заметно.

Ряд вещей. Основная проблема с файлами cookie заключается в том, что они передаются между браузером и сервером с каждым HTTP-запросом. Это нежелательное поведение, потому что чаще всего разработчики не хотят передавать локальные данные на сервер более одного раза, если вообще один раз. Cookies не дают разработчику выбора.

Как мы уже говорили ранее, Cookies могут хранить только до 4 КБ данных. Это не много данных, тем не менее 4 КБ достаточно для того, чтобы заметно замедлить запросы страниц.

Кроме того, файлы cookie передаются между клиентом и сервером в виде открытого текста. Следовательно, единственный способ защитить их — это зашифровать данные во время обмена данными с внутренним сервером с использованием SSL (Secure Socket Layer). Однако большинство веб-сайтов в Интернете не используют SSL, что оставляет хранилище открытым для прослушивания.

Есть и другие проблемы, которые делают файлы cookie менее полезными. В идеале разработчики хотят иметь возможность сохранять большие объемы данных на клиентском компьютере и не должны передавать эти данные на сервер снова и снова.

До сих пор мы не обсуждали нестандартные обходные пути для сохранения данных на клиентском компьютере. Когда разработчики Adobe (тогда еще называвшиеся Macromedia ) выпускали Flash Player 6, они тоже должны были решить ту же проблему. В 2002 году Flash Player 6 представил новую функцию под названием « Локальный общий объект» или, более часто, известную как « Flash Cookies», для эффективного внедрения тех же возможностей, что и стандартные файлы «cookie» HTTP, для Flash-фильмов и сайтов. Локальный общий объект позволил разработчикам сохранить по умолчанию 100 КБ данных на клиентском компьютере.

Второе решение — реализация Google Local Storage как части плагина Gears для веб-браузеров. Gears был (и я скажу вам, почему я использую это было в данный момент) комбинацией нескольких отсутствующих и полезных функций, необходимых для разработки многофункциональных интернет-приложений (RIA). Локальное хранилище Gears было основано на менее популярной спецификации Web SQL , использующей преимущества SQLite . Как вы уже догадались, Gears предоставил разработчикам полную базу данных SQL для хранения неограниченного объема данных на клиентском компьютере.

Разработчики Ajax Massive Storage System (AMASS) воспользовались этой возможностью и разработали стороннюю библиотеку JavaScript, которая позволила стандартным HTML-сайтам использовать функцию Local Shared Object во Flash или плагин Gears для сохранения данных в хранилище. клиентская машина. Кроме того, библиотека Dojo JavaScript позволяет обнаруживать наличие механизмов локального хранилища (например, Google Gears, Local Shared Object и т. Д.) И предоставляет унифицированный интерфейс для сохранения данных в разных веб-браузерах.

Вернемся к тому, почему я сказал, что Gears «был», а не «все еще есть»: это потому, что Google недавно объявил, что они прекратят дальнейшую разработку плагина Gears в пользу HTML5 и спецификации веб-хранилища, представленной в этом руководстве.

Теперь, когда урок истории закончен, мы собираемся изучить Web Storage и углубиться в некоторый код, чтобы лучше понять его. Самым простым способом описания веб-хранилища является возможность сохранения данных на клиентском компьютере в виде одного ключа для одного значения. Это очень похоже на то, как используются ассоциативные массивы:

1
{ «The Key» : «The Value» }

Локальное хранилище разработано так, чтобы оно изначально поддерживалось веб-браузерами. Это означает, что больше нет сторонних библиотек и работы с Flash. Удивительно, но веб-хранилище стало одной из наиболее успешных спецификаций с точки зрения принятия современными браузерами. Фактически, почти все современные браузеры поддерживают веб-хранилище, в том числе:

  • Internet Explorer 8+
  • Firefox 3.5+
  • Safari 4+
  • Опера 10.5+
  • iPhone Safari
  • Веб-браузер Android

Хотя веб-хранилище предназначено для обеспечения функциональности, аналогичной файлам cookie, оно было дополнительно усовершенствовано, чтобы не иметь каких-либо их негативных атрибутов. Например, веб-хранилище позволяет хранить до 5 МБ данных, что значительно увеличивает пространство по сравнению с объемом данных, которые можно сохранить в cookie-файле. Кроме того, сохранение данных с использованием веб-хранилища не приведет к отправке этих данных на серверную часть сервера при каждом запросе страницы. Это значительно повышает производительность. В соответствии со спецификацией веб-хранилища , веб-обозреватели истекают сохраненные данные с локального компьютера только по запросу пользователя и всегда будут избегать удаления данных во время выполнения сценария, который может получить доступ к этим данным.

Веб-браузеры предоставляют доступ к веб-хранилищу через объект localStorage в JavaScript. Один простой способ определить, может ли веб-браузер поддерживать веб-хранилище, — выполнить этот код JavaScript:

1
var webStorageSupported = (‘localStorage’ in window) && window[‘localStorage’] !== null;

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

Свойство length очень полезно. Он вернет количество пар ключ / значение, сохраненных в данный момент в локальном хранилище в текущем доступном домене:

1
alert(localStorage.length);

Если в локальном хранилище ранее не было сохранено ни одной пары ключ / значение, вышеупомянутый сценарий отобразит окно предупреждения с «0» в качестве сообщения, в противном случае сообщением будет количество сохраненных пар ключ / значение.

Метод setItem(key, value) просто сохраняет новую запись на локальном компьютере. Чтобы сохранить имя ключа со значением arman, мы можем выполнить этот скрипт:

1
localStorage.setItem(‘name’, ‘arman’);

Чтобы убедиться, что имя ключа действительно сохранено в локальном хранилище со значением arman, нам нужно использовать метод getItem(key) . Метод getItem просто принимает ключ и ищет в локальном хранилище соответствующий ключ, а затем возвращает его значение.

1
2
3
localStorage.setItem(‘name’, ‘arman’);
var value = localStorage.getItem(‘name’);
alert(value);

Если вы запустите вышеупомянутый скрипт, вы увидите окно предупреждения, содержащее слово arman, которое появится на экране, подтверждая, что мы успешно сохранили новую пару ключ / значение в локальном хранилище. Поскольку объект localStorage ведет себя подобно ассоциативным массивам, мы могли бы упростить приведенный выше скрипт, чтобы он выглядел следующим образом, и он все равно будет функционировать точно так же:

1
2
3
localStorage[‘name’] = ‘arman’;
var value = localStorage[‘name’];
alert(value);

Давайте посмотрим на метод removeItem(key) . Этот метод предназначен для удаления ранее сохраненной пары ключ / значение из локального хранилища. Если ключ не существует, этот метод просто ничего не делает. В следующем примере кода демонстрируется использование метода removeItem :

1
2
3
4
localStorage.setItem(‘name’, ‘arman’);
localStorage.removeItem(‘name’);
var value = localStorage.getItem(‘name’);
alert(value);

Когда приведенный выше скрипт выполняется, вы должны увидеть окно предупреждения со значением null в окне предупреждения. Используя ключ name , вышеприведенный скрипт просто создает новую пару ключ / значение и немедленно удаляет ее из локального хранилища. Впоследствии при доступе к локальному хранилищу с тем же ключом имени возвращается нулевое значение.

Придут случаи, когда возникнет необходимость полностью очистить локальное хранилище и начать с чистого листа. Метод clear() предназначен именно для этой цели. Этот метод автоматически очищает все ранее сохраненные пары ключ / значение из локального хранилища. Если нет записей, то ничего не изменится.

1
2
3
4
5
6
7
8
9
localStorage.setItem(‘name’, ‘arman’);
localStorage.setItem(‘name’, ‘smith’);
localStorage.setItem(‘name’, ‘frank’);
 
alert(localStorage.length);
 
localStorage.clear();
 
alert(localStorage.length);

Хотя вышеприведенный сценарий создает три новые пары ключ / значение (о чем свидетельствует первое предупреждение), вызов метода clear () удаляет все записи. Впоследствии во втором окне предупреждения будет отображаться сообщение «0».

Последний метод, который нам нужно рассмотреть, — это метод key(index) . Этот метод извлекает имя ключа на основе параметра индекса. localStorage поддерживает нулевой список всех записей внутри себя. Поэтому, чтобы получить доступ к первому ключу из локального хранилища, нам нужно использовать 0 в качестве индекса, как показано в этом сценарии:

1
2
3
localStorage.clear();
localStorage.setItem(‘age’, 5);
alert(localStorage.key(0));

Когда приведенный выше скрипт выполняется, он должен отобразить окно предупреждения с сообщением «возраст». Обратите внимание, как в приведенном выше примере первая строка кода очищает локальное хранилище. Это должно гарантировать, что мы начнем с чистого листа. Другое полезное применение метода key() связано со свойством length . Например, чтобы получить все пары ключ / значение из локального хранилища, не зная заранее ключей, мы могли бы написать скрипт, подобный следующему:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
localStorage.clear();
 
localStorage.setItem(«title», «Mr.»);
localStorage.setItem(«fullname», «Aaron Darwin»);
localStorage.setItem(«age», 17);
localStorage.setItem(«height», 182.5);
 
for(var i = 0; i < localStorage.length; i++)
{
    var keyName = localStorage.key(i);
    var value = localStorage.getItem(keyName);
     
    alert(keyName + » = » + value);
}

В приведенном выше сценарии наш код сначала очищает, а затем добавляет четыре новые пары ключ / значение в локальное хранилище. Затем он использует свойство length в цикле For для определения ключа для каждой пары ключ / значение. На каждой итерации цикла ключ присваивается переменной keyName которая затем передается методу getItem() для получения его значения.

При доступе к данным с использованием ключа, который не существует в локальном хранилище, вместо исключения всегда возвращается нулевое значение. Это затрудняет определение того, является ли значение ключа нулевым или просто отсутствует ключ в локальном хранилище.

Второй, о котором мы поговорим, это метод setItem(key, value) . Мы знаем, что можем передать любой тип значения методу setItem() для параметра value , но это не совсем так. В JavaScript мы можем создавать объекты Image . Однако текущая реализация веб-хранилища допускает сохранение только примитивных типов, таких как типы String , Boolean , Integer и Float . Поэтому мы можем ожидать, что следующий скрипт выдаст исключение и произойдет сбой:

1
2
var imageObject = new Image(«http://www.google.com/logo.gif»);
localStorage.setItem(«image», imageObject);

Кроме того, несмотря на то, что мы можем сохранять типы Boolean , Integer и Float , базовое представление этих типов возвращается к типу String . Это означает, что независимо от типа значения, переданного методу setItem() метод getItem() всегда будет возвращать значение типа String. Давайте посмотрим на пример скрипта:

1
2
3
4
5
var integerVariable = 34;
localStorage.setItem(«age», integerVariable);
var newVariable = localStorage.getItem(«age»);
 
alert(typeof(newVariable) == «number»);

В приведенном выше сценарии в конечном итоге значение integerVariable сохраняется как String. Поэтому, когда мы проверяем тип newVariable после получения его значения из локального хранилища, он больше не является целочисленным типом, и поэтому оператор сравнения будет иметь значение false. В таких ситуациях мы должны использовать удобные функции parseInt(String) и parseFloat(String) для преобразования. Для анализа Boolean типа данных нет функции NO, поэтому мы должны просто сравнить полученное значение со строками «истина» или «ложь».

Наконец, чтобы оператор сравнения в вышеприведенном сценарии стал равным true , мы должны изменить наш код для использования функции parseInt (), как показано в следующем сценарии:

1
2
3
4
5
var integerVariable = 34;
localStorage.setItem(«age», integerVariable);
var newVariable = parseInt(localStorage.getItem(«age»));
 
alert(typeof(newVariable) == «number»);

Теперь, когда приведенный выше скрипт выполняется, тип значения, сохраненного в newVariable будет Integer . Впоследствии утверждение сравнения оценивается как истинное .

Когда постоянные данные достигают квоты 5 МБ, веб-браузеры не предоставляют возможность запрашивать больше места в локальном хранилище. Вместо этого они QUOTA_EXCEEDED_ERR исключение QUOTA_EXCEEDED_ERR чтобы уведомить сценарий о том, что места больше нет. Самый простой способ обработать это исключение — перехватить исключение и уведомить пользователя о проблеме.

Кроме того, при перехвате исключения сценарий может удалить некоторые записи ключа / значения из локального хранилища, чтобы освободить место для новых пар.

Наконец, когда документ запрашивается непосредственно с диска, а не с веб-сервера, локальное хранилище будет очищаться каждый раз, когда вы уходите со страницы.

Стандарт веб-хранилища позволяет уведомлять сценарии, когда другие части кода добавляют, обновляют или удаляют записи из локального хранилища. Таким образом, всякий раз, когда что-то меняется в локальном хранилище, происходит событие хранилища . Однако в случае удаления записи с использованием несуществующего ключа событие не будет запущено. Это потому, что ничего не изменится в локальном хранилище. К сожалению, мои тесты показали, что в настоящее время браузеры Webkit (такие как Safari и Chrome) не запускают события хранения, как описано в спецификации веб-хранилища. Браузеры Internet Explorer, Firefox и Opera ведут себя должным образом.

Событие локального хранилища не может быть отменено. Его единственная цель — уведомить код пользователя об изменении, произошедшем внутри локального хранилища.

Следующий скрипт показывает, как прослушивать изменения в локальном хранилище, зарегистрировав обработчик события, который будет вызываться каждый раз при запуске события хранилища :

01
02
03
04
05
06
07
08
09
10
11
12
if (window.addEventListener) {
    window.addEventListener(«storage», handleStorageChange, false);
}
else
{
    window.attachEvent(«onstorage», handleStorageChange);
}
 
function handleStorageChange(event)
{
    alert(«Something was changed in the local storage»);
}

Поскольку ни одна версия Internet Explorer (за исключением общедоступного предварительного просмотра версии 9) не поддерживает систему обработки событий DOM Level 2, для регистрации обработчиков событий необходимо использовать метод attacheEvent() .

Аргумент event полезен, потому что он несет информацию об изменениях в локальном хранилище. Он придерживается интерфейса StorageEvent :

Свойство key определяет, какая пара ключ / значение была изменена. oldValue и newValue ведут себя так, как подсказывают их имена. То есть они соответственно содержат предыдущее и новое значение ключа.

Наконец, свойство url содержит адрес документа, для которого локальное хранилище было только что изменено. Однако из-за нескольких итераций реализации спецификации веб-хранилища в некоторых веб- url свойство url может быть реализовано как uri . Поэтому лучше сначала проверить свойство url а затем uri если свойство url не определено.

После прочтения этого я обычно хотел бы увидеть пример, прежде чем поверить всему этому. Как и я, я уверен, что многие из вас также хотели бы увидеть рабочий пример. Это не проблема, потому что я подготовил небольшой скрипт, который просто демонстрирует использование стандарта веб-хранилища в работающем приложении.

Он сделан таким образом, что его также можно протестировать с помощью iPhone Safari и веб-браузера Android. Идея состоит в том, чтобы браузер запомнил, какие ящики были открыты перед использованием локального хранилища. Я оставлю вас с этим кодом для игры:

001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
035
036
037
038
039
040
041
042
043
044
045
046
047
048
049
050
051
052
053
054
055
056
057
058
059
060
061
062
063
064
065
066
067
068
069
070
071
072
073
074
075
076
077
078
079
080
081
082
083
084
085
086
087
088
089
090
091
092
093
094
095
096
097
098
099
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
<!DOCTYPE html>
<html>
    <head>
        <meta name=»viewport» content=»width=240,user-scalable=no» />
        <style>
            * { padding: 0px;
            body { background: #333;
            h1 { font-size: 20px;
            p { padding: 5px;
            button { padding: 4px;
            #wrap { width: 240px;
            #blocks .unchecked, a { height: 40px;
            #blocks .unchecked { background: #000;
            #blocks .unchecked:hover { background: #333;
        </style>
         
        <script>
            var colorTable = {
                «0» : «blue»,
                «1» : «red»,
                «2» : «green»,
                «3» : «orange»,
                «4» : «purple»,
                «5» : «brown»,
                «6» : «gold»,
                «7» : «lime»,
                «8» : «lightblue»,
                «9» : «yellow»
            };
 
            function initializeGame()
            {
                if (browserSupportsLocalStorage() == false)
                {
                    alert(«This browser doesn’t support Web Storage standard»);
                    return;
                }
 
                var containerDiv = document.getElementById(«blocks»);
                 
                for (var i = 0; i < 10; i++)
                {
                    var id = i.toString();
                    var anchor = document.createElement(«a»);
                    anchor.id = id;
                    anchor.innerHTML = i + 1;
                     
                    if (localStorage.getItem(id) != null)
                    {
                        anchor.style.backgroundColor = colorTable[id];
                    }
                    else
                    {
                        anchor.className = «unchecked»;
 
                        if (anchor.addEventListener)
                        {
                           anchor.addEventListener(«click», handleBoxClick, false);
                        }
                        else
                        {
                           anchor.attachEvent(«onclick», handleBoxClick);
                        }
                    }
                     
                    containerDiv.appendChild(anchor);
                }
            }
             
            function handleBoxClick(e)
            {
                var target = (e.target) ?
 
                if (target.className == «»)
                    return;
 
                var id = target.id;
                var color = colorTable[id]
                target.className = «»;
                target.style.backgroundColor = colorTable[id];
                 
                localStorage.setItem(id, color);
            }
             
            function resetGame()
            {
                var containerDiv = document.getElementById(«blocks»);
                containerDiv.innerHTML = «»;
                localStorage.clear();
                initializeGame();
            }
 
            function browserSupportsLocalStorage()
            {
                return (‘localStorage’ in window) && (window[‘localStorage’] != null);
            }
        </script>
    </head>
    <body onload=»initializeGame();»>
        <div id=»wrap»>
            <h1>Web Storage Demo</h1>
            <p>
                This page was design to simply demonstrate the use of Web Storage in modern browsers.
                that haven’t yet been clicked remain black.
                The browser however will remember which boxes are clicked, even when you navigate away from this page.
                Go on, give it a try.
            </p>
            <div id=»blocks»>
            </div>
            <button onclick=»resetGame()»>Reset Game</button>
            <button onclick=»document.location.reload(true);»>Refresh Page</button>
        </div>
    </body>
</html>