LocalStorage это локальное хранилище данных (key, value), расположенное на клиенте. Данные хранятся до того пока пользователь не очистит их принудительно (историю броузера) или с помощью JavaScript.

Работа с localStorage достаточна проста, данные хранятся в виде строк:

// Сохранение значения
localStorage.setItem(key, value)
localStorage[key] = value
// Получение значения
localStorage.getItem(key)
localStorage[key]
// Удаление значения
localStorage.removeItem(key)
delete localStorage[key]
// Очистка всего хранилища
localStorage.clear()

Самое интересное здесь событие window storage, которое генерируется при изменении значения элемента localStorage.

window.addEventListener('storage', function(events) {
    console.dir(events);
});

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

В целях изучения данной технологии мной был написан небольшой скрипт для демонстрации работы localStorage с вкладками броузера.

Попутно, для удобства, был добавлен функционал позволяющий расширить localStorage, хранить и извлекать из localStorage типы значений Null, Date, Number, Boolean, Function

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

<script src="storage.js"></script>

Варианты установки значений

_storage('name', 'Ivan');
_storage('test', [1, 2, 3]);
_storage('obj', {van: 1, two: 2});
_storage('mydate', new Date);
_storage('fn', function(){
    alert('Hi');
};
var storage = _storage();
storage.set('name', 'Ivan');
storage
    .set('test', [1, 2, 3])
    .set('mydate', new Date)
    .set('obj', {van: 1, two: 2});

Варианты получения значений:

console.log(_storage('name')); // Ivan
console.dir(_storage('test')); // [1, 2, 3] type: [object Array]
console.dir(_storage('mydate')); // type: [object Date];
console.dir(_storage('fn')); // type: [object Function]
console.dir(_storage('obj')); // type: [object Object]
var storage = _storage();
alert(storage.get('name'));
_storage('fn')(); // call function
storage.get('fn')(); // call function

Подписаться на изменение значения:

 _storage().subscribe('message', function (value, e) {
     // value - new value
     // e - event object
 });

 var storage = _storage();
 storage.subscribe('message', function (value, e) {
    // value - new value
    // e - event object
}).subscribe('fn', function (fn, e) {
     fn(); //call function
    // fn - new value
    // e - event object
});

Проще один раз увидеть:

git clone https://github.com/Poznakomlus/localstorage.git
cd localstorage
npm install
node run.js

При этом у нас запустится сервер по адресу http://127.0.0.1:7777 и в браузере откроются две страницы этого сервера (Sender, Recipient) для подробной демонстрации описанного. Вы сможете передавать данные из страницы Sender странице Recipient.

Пользуйтесь на здоровье.
Поделиться с друзьями
-->

Комментарии (39)


  1. PaulMaly
    14.12.2016 14:02
    +2

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


    1. Frozik
      14.12.2016 14:20

      Текущая вкладка и так знает, что и когда меняет, а вот для атомарности нужно отделять свои и чужие события.


      1. vintage
        14.12.2016 14:57

        Что такое "атомарность"?


        1. Frozik
          14.12.2016 15:16
          +1

          Есть достаточно неплохая статья.


          1. vintage
            14.12.2016 16:19

            Можно вкратце по-русски?


            1. Frozik
              15.12.2016 10:48

              Проблему можно описать следующим способом — в хранилище у нас есть некий ключ, который мы увеличиваем на 1 каждый раз, когда вызываем некую функцию. Сложность состоит в том, что для того, чтобы увеличить значение нам сначала надо прочитать старое. У нас получается 2 операции — чтение и запись нового значения. Исходя из этого могут возникнуть проблемы, что 2 конкурирующие вкладки могут перезаписать значения друг друга. Конечно в браузерах есть специальные механизмы защиты от этого поведения (storage mutex), но операция остается не атомарной, то есть 2 операции чтения и записи не выполняются как одна.


              1. vintage
                15.12.2016 11:51

                Так, и при чём тут события в той же вкладке?


                1. Frozik
                  15.12.2016 13:14

                  Чтобы мы могли гарантированно отличить наши и чужие вмешательства в хранилище. Иначе мы этого сделать не сможем (конечно в StorageEvent можно добавить параметр для определения того, что это мы недавно сделали изменения, а не в другой вкладке, но это выглядит как костыль). Так как мне не приходилось сталкиваться с такой проблемой на практике, и я не учил детально матчасть, но могу указать стартовую точку, с которой это можно сделать.


                  1. vintage
                    15.12.2016 13:54

                    А какая разница наше это вмешательство или не наше? Оно в любом случае будет неатомарное.


                    1. Frozik
                      15.12.2016 14:30

                      Прошу прощения, я некорректно высказался выше про атомарность. Я имел в виду не как её реализовать на событиях, а как бороться с проблемами. Мы стандартными средствами не сможем добиться атомарности (хотя тут человек говорит, что он сделать блокировку — первая статья), но мы сможем устранить проблемы.


                  1. PaulMaly
                    15.12.2016 15:34

                    Зачем мне гарантированно отличать из какой вкладки пришли изменения, если меня всего лишь интересует какое значение сейчас в сторе и каким оно было до этого. Все эти данные есть в ивенте. Кто конкретно последним перезаписал значение меня не интересует, только уведомление об итоговом результате.

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


                    1. RubaXa
                      15.12.2016 15:39

                      Именно, никто не мешал добавить event.source, как у postMessage.


                      1. PaulMaly
                        15.12.2016 15:58

                        Собственно у меня такая же ассоциация возникла. Иными словами тут проблемы вообще нет и ограничение искусственное.


                      1. Frozik
                        15.12.2016 16:11

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


                        1. RubaXa
                          15.12.2016 16:17

                          К какому другому? Какую синхронизацию? Почему этих проблем нет у postMessage?


      1. PaulMaly
        14.12.2016 19:21
        +5

        Что значит «знает»? Это ведь смотря как писать. Если вы пишете в «event-driven» стиле, то «знает» это слишком громко.

        Лично для меня возможность подписываться на изменения в хранилище было бы хорошим юзкейсом. Даже в том примитивном виде как сейчас работает между вкладками. Опять же главный вопрос, почему нет? (и атомарность тут ни при чем).


        1. Frozik
          15.12.2016 13:29

          Ну для этого и можно использовать библиотеки, на подобие того, что создал автор данного поста. См. коммент выше.


          1. PaulMaly
            15.12.2016 15:06

            Да причем тут библиотеки то? Лично я давно и вполне успешно использую localForage от мазилы. Но вопрос то опять не в этом. Если ивент уже есть, почему он так искусственно ограничен?


  1. imgen
    14.12.2016 14:51
    +1

    Что-то подобное уже где-то встречал, например вот тут
    wormhole
    да и не только встречал, а активно использовал и использую.


    1. vintage
      14.12.2016 15:05

      .


  1. vintage
    14.12.2016 15:05

    Зачем вы функции-то сериализуете?


    А ещё вы можете записать строку ":b:hello", а прочитать false.


    1. RubaXa
      14.12.2016 22:02
      +2

      А чего вы минусует vintage, он всё правильно задал вопрос, стерилизовать функции глупость, любая внешняя зависимость из родительского scope и при десереиализации будет вызрыв.


      Единственно корректный способ стерилизации/десерилазиции из коробки, это JSON.parse и JSON.stringinfy.


      Так что я бы не советовал использовать эту поделку, а взять нормальный store.js


      1. vlasenkofedor
        14.12.2016 22:35
        -1

        стерилизовать функции глупость

        стерилизовать это как? Не выдумывайте новые понятия.
        Смотрите внимательно код. Для функций используется toString
        не советовал использовать эту поделку

        Простите, но я нечего не подделывал и не собирался


        1. RubaXa
          14.12.2016 22:40
          +2

          стерилизовать это как? Не выдумывайте новые понятия.

          Мило :] На это и закончим.


          1. Carduelis
            15.12.2016 13:51
            -1

            Вы бы поделились с сообществом, как вы функции стерилизуете. А то гугл мне все про собак да кошек выдает. Не думаю, что большинство знает о чем вы.


            1. RubaXa
              15.12.2016 13:56

              Ну не надо ерничать, да ещё и сообщество принижать, всем и так понятно о чем там написано, несмотря на автокоррекцию.


  1. RubaXa
    14.12.2016 17:48
    +3

    Доступ и работа с localStorage без try catch, а-та-тат, исправьте, пока кто-нибудь не поранился.


    StorageEvent вызывается каждый раз, когда произошло изменение в localStorage.

    Неа, не всегда, в Chrome есть баг, при измененном document.domain события не будет, надо детектить и делать хоть какой-то fallback через setTimeout.


    1. vlasenkofedor
      14.12.2016 22:25

      работа с localStorage без try catch, а-та-тат, исправьте

      Покажите где в данном примере не хватает по вашему убеждению try catch


      1. RubaXa
        14.12.2016 22:39

        Везде, о чем я и написал, но мне не сложно, вот:



        1. vintage
          14.12.2016 23:03

          Установка значения может упереться в отсутствие памяти, но то может произойти с чтением значения?


          1. RubaXa
            14.12.2016 23:22

            localStorage может быть банально битым, или определенные политики безопасности полностью запрещают к нему доступ, но там эксепшен будет уже на стадии window.localStorage.


        1. vlasenkofedor
          15.12.2016 04:48

          Простите, но мы по разному думаем. В вашем предложении я должен обернуть в try catch нативные методы по работе с Loalstorage
          В документации по ним нет упоминаний, что выбрасываются исключения.
          Далее, вовсе не понятно, что-же я в таком случае должен вернуть и как отрабатывать должен остальной участок кода.
          Я говорю о том, что в приложении (не в storage.js) стоит раз проверять на доступность данной (localstorage) функции и затем использовать функции _storage.
          А не оборачивать логику ненужными проверками. Логику не только storage, но и логику приложения. И еще раз хочу отметить, что это пример, это не библиотека (нет тестов, документации и т.д.) Пример демонстрации работы. Пощупать, увидеть глазами как это работает.


          1. RubaXa
            15.12.2016 08:33

            В документации всё есть, ищите лучше.


          1. imgen
            15.12.2016 10:51
            +1

            нужно смотреть в сюда


            1. vlasenkofedor
              15.12.2016 12:03

              imgen, спасибо, не знал о таком поведении браузеров
              Технология доступна и в то-же время исключение.


            1. vlasenkofedor
              15.12.2016 13:03

              Тогда перед использованием

              function storageEnable() {
                var state = false;
                try {
                  window.localStorage.setItem('test', 1);
                  state = window.localStorage.getItem('test') == 1;
                  window.localStorage.removeItem('test');
                } catch (err) {
                  console.log(err);
                }
                return state;
              }
              console.log(storageEnable()); // утка :-)
              


  1. kapuletti
    14.12.2016 18:42
    +1

    Данные хранятся до того пока пользователь не очистит их принудительно (историю броузера) или с помощью JavaScript.


    Или до тех пор, пока браузер сам не решит удалить данные, как например это делает Safari в iOS при недостатке свободного места.


  1. madMxg
    14.12.2016 21:25
    +1

    Есть же отличная библиотека от Mozilla localForage
    Event'ы навешать можно вот этим localForage-observable


  1. Sombressoul
    14.12.2016 22:45
    +1

    Что касается расшаривания событий, данных и задач между вкладками (в обход сервера), то я пару лет назад писал на эту тему.