Использование веб-хранилища на стороне клиента

Роберт Ниман| 28 июля 2010 г.

HTML5 API и сопутствующие технологии дают веб-разработчикам много полезных возможностей. Одна из них — возможность хранить информацию на клиентском компьютере. До сих пор можно было только писать скрипты с файлами "cookie". Конечно, этот подход был далек от идеала. Простых свойств или методов обработки данных с помощью файлов "cookie" не было; чтобы сделать хотя бы приблизительно то, что нужно, в дистанционном режиме, приходилось пользоваться нудными процедурами обработки строк. Еще одним крупным недостатком этого подхода был предельный размер файлов "cookie". Как правило, он составляет 4096 байт, что не так уж много. Следовательно, необходимо было искать другие методы.

Вводные сведения о localStorage и sessionStorage

С появлением новой спецификации веб-хранилища возникли новые, расширенные возможности использования объектов localStorage и sessionStorage с тем же синтаксисом, но другими областями применения и объемом доступного пространства для хранения 5 МБ (как минимум)! Объект localStorage — долговременное хранилище. Пользователь может закрыть веб-браузер и открыть его на следующий день, сохранив все данные, привязанные к текущему доменному контексту. Данные можно удалить либо программно, либо путем очистки кэша пользователем. Объект localStorage предназначен для долговременного хранения, а название объекта sessionStorage полностью отражает срок хранения - данные хранятся в течение конкретного сеанса и удаляются сразу после его закрытия. К сожалению, на данный момент только с помощью различных инструментов для разработки веб-браузеров можно узнать, что именно сохраняется. Соответственно, процесс явно недостаточно прозрачен для менее технически подкованных пользователей.

Хорошо то, что такое веб-хранилище поддерживают все текущие версии распространенных веб-браузеров, в том числе:

  • Internet Explorer 8+
  • Firefox 3.5+
  • Google Chrome 4+
  • Safari 4+
  • Firefox 10.5+

Кроме того, его поддерживают некоторые мобильные телефоны на платформе iPhone OS 2 и Android 2

Написание кода

Для начала рассмотрим пример самого простого кода. Я использую в этих примерах объект localStorage, но все сказанное относится и к объекту sessionStorage. Прежде всего, сохраним мое имя в объекте localStorage:

localStorage.setItem("name", "Robert");

Довольно просто, да? Сравните эту строку с операциями обработки файлов "cookie" которые пришлось бы выполнить. Считывается это значение примерно так:

localStorage.getItem("name");

И, наконец, когда имя Роберт нам надоест, значение легко будет убрать:

localStorage.removeItem("name");

Можно убрать все сохраненные значения одновременно:

localStorage.clear();

Не просто сохранение текста

В примерах из предыдущего раздела сохранялись данные в текстовом формате. Разумеется, время от времени нужно сохранить и что-нибудь посложнее. Согласно спецификации объекты localStorage и sessionStorage поддерживают только сохранение текста, но мы, мудрые люди, сможем выполнить более сложные операции с помощью JSON. Все веб-браузеры с поддержкой веб-хранилищ, к счастью, имеют встроенную поддержку JSON. Давайте этим воспользуемся! В этом примере сначала нужно создать простой объект JavaScript, а затем с помощью метода преобразования в строку объекта JSON сохранить его в виде строки в объекте localStorage. При считывании сохраненного значения можно будет снова преобразовать его в объект JavaScript методом parse у объекта JSON.

// Использование JSON

var info = {

    "language" : "Swedish",

    "occupation" : "Web Developer"

};

    

// Сохранение в качестве строки

localStorage.setItem("info", JSON.stringify(info));

    

// Загрузка в виде объекта JSON

console.log(JSON.parse(localStorage.info));

Небольшой пример

Предположим, вы создаете какой-то почтовый веб-клиент. Разумеется, при чтении сообщения и т.д. понадобится динамическое обновление какой-то части страниц в Ajax. Важно, чтобы после открытия сообщения пользователем информация сохранялась и просто извлекалась из объекта localStorage, а не скачивалась с сервера еще раз.

Код из этого примера получает все элементы страницы с именем класса "example", считывает все данные из этого конкретного сообщения и сохраняет их в объекте localStorage. В данном коде не используются библиотеки JavaScript, но вы, конечно, сможете сделать то же самое и с помощью любимой библиотеки.

<div id="unique-message-id-1" class="message">
    <dl>
        <dt>From</dt>
        <dd class="sender">bill@microsoft.com</dd>
        <dt>To</dt>
        <dd class="recipients">robert@robertnyman.com</dd>
        <dt>Date</dt>
        <dd class="date">Mon, Jun 14, 2010 at 2:23 AM</dd>
        <dt>Subject</dt>
        <dd class="subject">Interested in running a company?</dd>
        <dt>Message</dt>
        <dd class="message-content">
            Hi Robert, I wonder if you would like to run a major company?
        </dd>
    </dl>
</div>
<div id="unique-message-id-2" class="message">
    <dl>
        <dt>From</dt>
        <dd class="sender">robert@robertnyman.com</dd>
        <dt>To</dt>
        <dd class="recipients">bill@microsoft.com</dd>
        <dt>Date</dt>
        <dd class="date">Mon, Jun 14, 2010 at 3:42 AM</dd>
        <dt>Subject</dt>
        <dd class="subject">RE: Interested in running a company?</dd>
        <dt>Message</dt>
        <dd class="message-content">
            Hi Bill, Thanks for the offer, but that's not really my thing.
        </dd>
    </dl>
</div>

Код JavaScript

/*

    getElementsByClassName developed by Robert Nyman, http://robertnyman.com

    Code/licensing: http://code.google.com/p/getelementsbyclassname/

*/

var getElementsByClassName = function (className, tag, elm){

    if (document.getElementsByClassName) {

        getElementsByClassName = function (className, tag, elm) {

            elm = elm || document;

            var elements = elm.getElementsByClassName(className),

                nodeName = (tag)? new RegExp("\\b" + tag + "\\b", "i") : null,

                returnElements = [],

                current;

            for(var i=0, il=elements.length; i<il; i+=1){

                current = elements[i];

                if(!nodeName || nodeName.test(current.nodeName)) {

                    returnElements.push(current);

                }

            }

            return returnElements;

        };

    }

    else if (document.evaluate) {

        getElementsByClassName = function (className, tag, elm) {

            tag = tag || "*";

            elm = elm || document;

            var classes = className.split(" "),

                classesToCheck = "",

                xhtmlNamespace = "http://www.w3.org/1999/xhtml",

                namespaceResolver = (document.documentElement.namespaceURI === xhtmlNamespace)? xhtmlNamespace : null,

                returnElements = [],

                elements,

                node;

            for(var j=0, jl=classes.length; j<jl; j+=1){

                classesToCheck += "[contains(concat(' ', @class, ' '), ' " + classes[j] + " ')]";

            }

            try    {

                elements = document.evaluate(".//" + tag + classesToCheck, elm, namespaceResolver, 0, null);

            }

            catch (e) {

                elements = document.evaluate(".//" + tag + classesToCheck, elm, null, 0, null);

            }

            while ((node = elements.iterateNext())) {

                returnElements.push(node);

            }

            return returnElements;

        };

    }

    else {

        getElementsByClassName = function (className, tag, elm) {

            tag = tag || "*";

            elm = elm || document;

            var classes = className.split(" "),

                classesToCheck = [],

                elements = (tag === "*" && elm.all)? elm.all : elm.getElementsByTagName(tag),

                current;

                returnElements = [],

                match;

            for(var k=0, kl=classes.length; k<kl; k+=1){

                classesToCheck.push(new RegExp("(^|\\s)" + classes[k] + "(\\s|$)"));

            }

            for(var l=0, ll=elements.length; l<ll; l+=1){

                current = elements[l];

                match = false;

                for(var m=0, ml=classesToCheck.length; m<ml; m+=1){

                    match = classesToCheck[m].test(current.className);

                    if (!match) {

                        break;

                    }

                }

                if (match) {

                    returnElements.push(current);

                }

            }

            return returnElements;

        };

    }

    return getElementsByClassName(className, tag, elm);

};

 

window.onload = function () {

    // Создание объекта JavaScript для сообщений и выбор элементов с классом "message"

    var messagesToSave = {},

        messages = getElementsByClassName("message");

        

    // Итерация по всем сообщениям на странице и получение из них данных    

    for (var i=0, il=messages.length, message; i<il; i++) {

        message = messages[i];

        messagesToSave[message.id] = {

            "from" : getElementsByClassName("sender", "dd", message)[0].innerHTML,

            "to" : getElementsByClassName("recipients", "dd", message)[0].innerHTML,

            "date" : getElementsByClassName("date", "dd", message)[0].innerHTML,

            "subject" : getElementsByClassName("subject", "dd", message)[0].innerHTML,

            "message" : getElementsByClassName("message-content", "dd", message)[0].innerHTML

        };

    };

    

    // Преобразование данных в строку и сохранение в объекте localStorage

    localStorage.setItem("messages", JSON.stringify(messagesToSave));

    

    // Считывание сохраненных данных и преобразование в объект JavaScript

    console.log(JSON.parse(localStorage.getItem("messages")));

};

Что делать со старыми веб-браузерами?

Конечно, все новейшие веб-браузеры поддерживают веб-хранилища, а более старые по большей части довольно быстро выходят из употребления. Тем не менее, все же необходимо позаботиться о пользователях Internet Explorer 7, а может быть и Internet Explorer 6. Так что же с ними делать?

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

Альтернативное хранилище в сети

Были попытки дополнить веб-хранилище более продвинутыми функциями обработки базы данных, а конкретно Web SQL Database и Indexed Database API. На данный момент браузеры Google Chrome, Safari и Opera реализованы с использованием Web SQL Database API, а Майкрософт и создатели Firefox заявляют о поддержке альтернативной спецификации IndexedDB, предложенной Oracle. Хотя на момент написания этой статьи Indexed Database API не был реализован ни в одном официально выпущенном веб-браузере, Mozilla сейчас над этим работает, Майкрософт, по-видимому, обеспечивает поддержку, а Google выражает к этому интерес. Будущее покажет, но мне представляется, что производители браузеров сделают Indexed Database API стандартным интерфейсом для работы с базами данных в Интернете.

Новый мир хранения!

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