Web Storage API: примеры использования

от автора

Доброго времени суток, друзья!

В данной статье мы рассмотрим парочку примеров использования Web Storage API или объекта «Storage».

Что конкретно мы будем делать?

  • Научимся запоминать время воспроизведения видео.
  • Поработаем с формой входа на страницу.
  • Напишем логику списка задач (todo list).
  • Схематично набросаем корзину для товаров.

Итак, поехали.

Краткий обзор

Объект «Storage» используется для хранения данных на стороне клиента и в этом смысле выступает альтернативой cookies. Преимущество Storage состоит в размере хранилища (от 5 Мб, зависит от браузера, при превышении лимита выбрасывается ошибка «QUOTA_EXCEEDED_ERR») и отсутствии необходимости обращаться к серверу. Существенный недостаток — безопасность: стоит вредоносному скрипту получить доступ к странице, и пиши пропало. Поэтому крайне не рекомендуется хранить в Storage конфиденциальную информацию.

Справедливости ради стоит отметить, что на сегодняшний день существуют более продвинутые решения для хранения данных на стороне клиента — это IndexedBD, Service Workers + Cache API и др.

О сервис-воркерах можно почитать здесь.

Web Storage API включает в себя localStorage и sessionStorage. Разница между ними состоит во времени хранения данных. В localStorage данные хранятся постоянно до их «явного» удаления (ни перезагрузка страницы, ни ее закрытие не приводят к удалению данных). Время хранения данных в sessionStorage, как следует из названия, ограничено сессией браузера. Поскольку sessionStorage на практике почти не используется, мы будет рассматривать только localStorage.

Что необходимо знать о localStorage?

  • При использовании localStorage создается представление объекта «Storage».
  • Данные в localStorage хранятся в виде пар ключ/значение.
  • Данные хранятся в виде строк.
  • Данные не сортируются, что иногда приводит к их перемешиванию (в чем мы убедимся на примере списка задач).
  • При включении в браузере режима инкогнито или приватного режима использование localStorage может стать невозможным (зависит от браузера).

localStorage обладает следующими методами:

  • Storage.key(n) — имя ключа с индексом n
  • Storage.getItem() — получить элемент
  • Storage.setItem() — записать элемент
  • Storage.removeItem() — удалить элемент
  • Storage.clear() — очистить хранилище
  • Storage.length — длина хранилища (количество элементов — пар ключ/значение)

В спецификации это выглядит так:

interface Storage {     readonly attribute unsigned long length;     DOMString? key(unsigned long index);     getter DOMString? getItem(DOMString key);     setter void setItem(DOMString key, DOMString value);     deleter void removeItem(DOMString key);     void clear(); }; 

Данные в хранилище записываются одним из следующих способов:

localStorage.color = 'deepskyblue' localStorage[color] = 'deepskyblue' localStorage.setItem('color', 'deepskyblue') // рекомендуется использовать этот способ 

Получить данные можно так:

localStorage.getItem('color') localStorage['color'] 

Как перебрать ключи хранилища и получить значения?

// способ 1 for (let i = 0; i < localStorage.length; i++) {     let key = localStorage.key(i)     console.log(`${key}: ${localStorage.getItem(key)}`) }  // способ 2 let keys = Object.keys(localStorage) for (let key of keys) {     console.log(`${key}: ${localStorage.getItem(key)}`) } 

Как мы отмечали выше, данные в хранилище имеют строковый формат, поэтому с записью объектов возникают некоторые трудности, которые легко решаются с помощью тандема JSON.stringify() — JSON.parse():

localStorage.user = {     name: 'Harry' } console.dir(localStorage.user) // [object Object]  localStorage.user = JSON.stringify({     name: 'Harry' }) let user = JSON.parse(localStorage.user) console.dir(user.name) // Harry 

Для взаимодействием с localStorage существует специальное событие — storage (onstorage), которое возникает при записи/удалении данных. Оно имеет следующие свойства:

  • key — ключ
  • oldValue — старое значение
  • newValue — новое значение
  • url — адрес хранилища
  • storageArea — объект, в котором произошло изменение

В спецификации это выглядит так:

[Constructor(DOMString type, optional StorageEventInit eventInitDict)]     interface StorageEvent : Event {     readonly attribute DOMString? key;     readonly attribute DOMString? oldValue;     readonly attribute DOMString? newValue;     readonly attribute DOMString url;     readonly attribute Storage? storageArea; };  dictionary StorageEventInit : EventInit {     DOMString? key;     DOMString? oldValue;     DOMString? newValue;     DOMString url;     Storage? storageArea; }; 

Допускает ли localStorage прототипирование?

Storage.prototype.removeItems = function() {     for (item in arguments) {         this.removeItem(arguments[item])     } }  localStorage.setItem('first', 'some value') localStorage.setItem('second', 'some value')  localStorage.removeItems('first', 'second')  console.log(localStorage.length) // 0 

Как проверить наличие данных в localStorage?

// способ 1 localStorage.setItem('name', 'Harry')  function isExist(name) {     return (!!localStorage[name]) }  isExist('name') // true  // способ 2 function isItemExist(name) {     return (name in localStorage) }  isItemExist('name') // true 

В браузере localStorage можно найти здесь:

Довольно слов, пора переходить к делу.

Примеры использования

Запоминаем время воспроизведения видео

window.onload = () => {     // находим элемент <video>     let video = document.querySelector('video')          // если localStorage содержит значение currentTime (текущее время), присваиваем это значение video.currentTime     if(localStorage.currentTime) {         video.currentTime = localStorage.currentTime     }          // при каждом изменении video.currentTime, записываем его значение в localStorage.currentTime     video.addEventListener('timeupdate', () => localStorage.currentTime = video.currentTime) } 

Результат выглядит так:

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

Codepen

Github

Работаем с формой для входа

Разметка выглядит так:

<form>     Login: <input type="text">     Password: <input type="text">     <input type="submit"> </form> 

У нас имеется форма и три «инпута». Для пароля мы используем <input type = «text»>, поскольку если использовать правильный тип (password), Chrome будет пытаться сохранять введенные данные, что помешает нам реализовать собственный функционал.

JavaScript:

// находим форму и инпуты для ввода логина и пароля let form = document.querySelector('form') let login = document.querySelector('input') let password = document.querySelector('input + input')  // если localStorage не пустой // получаем из него необходимые данные // и присваиваем их инпутам if (localStorage.length != 0) {     login.value = localStorage.login     password.value = localStorage.password }  // вешаем на форму обработчик события "submit" form.addEventListener('submit', () => {     // записываем введенные пользователем данные в localStorage     localStorage.login = login.value     localStorage.password = password.value          // если пользователем введены hello и world в качестве логина и пароля, соответственно     // используем древний метод для вывода сообщения "welcome" на страницу     if (login.value == 'hello' && password.value == 'world') {         document.write('welcome')     } }) 

Мы не «валидируем» форму, потому что наша цель — изучить возможности localStorage. Отсутствие валидации, в частности, позволяет записывать пустые строки в качестве логина и пароля.

Результат выглядит так:

Вводим волшебные слова.

Данные записываются в localStorage, а на страницу выводится приветствие.

Codepen

Github

Пишем логику списка задач

Разметка выглядит так:

<input type="text"><button class="add">add task</button><button class="clear">clear storage</button>  <ul></ul> 

У нас имеется «инпут» для ввода задачи, кнопка для добавления задачи в список, кнопка для очистки списка и хранилища и контейнер для списка.

JavaScript:

// находим инпут и фокусируемся на нем let input = document.querySelector('input') input.focus() // находим кнопку для добавления задачи в список let addButton = document.querySelector('.add') // находим контейнер для списка let list = document.querySelector('ul')  // если в localStorage имеются данные if (localStorage.length != 0) {     // цикл по количеству пар ключ/значение     for (let i = 0; i < localStorage.length; i++) {         let key = localStorage.key(i)         // получаем шаблон - элемент списка         let template = `${localStorage.getItem(key)}`         // помещаем задачу в список         list.insertAdjacentHTML('afterbegin', template)     }          // находим все кнопки с классом "close" - галочки для выполненных задач     document.querySelectorAll('.close').forEach(b => {         // для каждой кнопки         b.addEventListener('click', e => {             // получаем родительский элемент "li"             let item = e.target.parentElement             // удаляем задачу из списка             list.removeChild(item)             // удаляем данные из localStorage             localStorage.removeItem(`${item.dataset.id}`)         })     }) }  // возможность добавления задачи в список при нажатии клавиши "Enter" window.addEventListener('keydown', e => {     if (e.keyCode == 13) addButton.click() })  // вешаем на кнопку для добавления задачи в список обработчик события "клик" addButton.addEventListener('click', () => {     // получаем строку - задачу     let text = input.value     // формируем шаблон, запись и идентификация значений по ключам осуществляется через атрибут "data-id"     let template = `<li data-id="${++localStorage.length}"><button class="close">V</button><time>${new Date().toLocaleDateString()}</time> <p>${text}</p></li>`     // добавляем шаблон - задачу в список     list.insertAdjacentHTML('afterbegin', template)     // записываем данные в localStorage     localStorage.setItem(`${++localStorage.length}`, template)     // сбрасываем значение инпута     input.value = ''      // вешаем на кнопку для удаления задачи из списка обработчик события "клик"     document.querySelector('.close').addEventListener('click', e => {         // получаем элемент списка - родительский элемент кнопки         let item = e.target.parentElement         // удаляем задачу из списка         list.removeChild(item)         // удаляем данные из localStorage         localStorage.removeItem(`${item.dataset.id}`)     }) })  // вешаем на кнопку для очистки обработчик события "клик" document.querySelector('.clear').onclick = () => {     // очищаем хранилище     localStorage.clear()     // удаляем задачи из списка     document.querySelectorAll('li').forEach(item => list.removeChild(item))     // фокусируемся на инпуте     input.focus() } 

Результат выглядит так:

Задачи, добавляемые в список, сохраняются в localStorage в виде готовой разметки. При перезагрузке страницы список формируется из данных хранилища (имеет место перемешивание, о котором упоминалось выше).

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

Codepen

Github

Схема корзины для товаров

Мы не преследуем цель создать полнофункциональную корзину, поэтому код будет написан «в старом стиле».

Разметка одного товара выглядит так:

<div class="item">     <h3 class="title">Item1</h3>     <img src="http://placeimg.com/150/200/tech" alt="#">     <p>Price: <span class="price">1000</span></p>     <button class="add" data-id="1">Buy</button> </div> 

У нас имеется контейнер для товара, наименование, изображение и цена товара, а также кнопка для добавления товара в корзину.

Также у нас имеется контейнер для кнопок отображения содержимого корзины и очистки корзины и хранилища и контейнер для корзины.

<div class="buttons">     <button id="open">Cart</button>     <button id="clear">Clear</button> </div> <div id="content"></div> 

JavaScript:

// находим товары и корзину let itemBox = document.querySelectorAll('.item'),     cart = document.getElementById('content');  // получаем данные из localStorage function getCartData() {     return JSON.parse(localStorage.getItem('cart')); }  // записываем данные в хранилище function setCartData(o) {     localStorage.setItem('cart', JSON.stringify(o)); }  // добавление товара в корзину function addToCart() {     // блокируем кнопку на время работы с корзиной     this.disabled = true;     // получаем данные из корзины или создаем новый объект, если данные отсутствуют     let cartData = getCartData() || {},         // родительский элемент кнопки "Buy"         parentBox = this.parentNode,         // id товара         itemId = this.getAttribute('data-id'),         // название товара         itemTitle = parentBox.querySelector('.title').innerHTML,         // стоимость товара         itemPrice = parentBox.querySelector('.price').innerHTML;     // +1 к товару     if (cartData.hasOwnProperty(itemId)) {         cartData[itemId][2] += 1;     } else {         // + товар         cartData[itemId] = [itemTitle, itemPrice, 1];     }     // обновляем данные в localStorage     if (!setCartData(cartData)) {         // снимаем блокировку кнопки         this.disabled = false;     } }  // устанавливаем обработчик события "клик" на каждую кнопку "Buy" for (let i = 0; i < itemBox.length; i++) {     itemBox[i].querySelector('.add').addEventListener('click', addToCart) }  // содержимое корзины function openCart() {     // получаем данные из хранилища     let cartData = getCartData(),         totalItems = '',         totalGoods = 0,         totalPrice = 0;     // формируем данные для вывода     if (cartData !== null) {         totalItems = '<table><tr><th>Name</th><th>Price</th><th>Amount</th></tr>';         for (let items in cartData) {             totalItems += '<tr>';             for (let i = 0; i < cartData[items].length; i++) {                 totalItems += '<td>' + cartData[items][i] + '</td>';             }             totalItems += '</tr>';             totalGoods += cartData[items][2];             totalPrice += cartData[items][1] * cartData[items][2];         }         totalItems += '</table>';         cart.innerHTML = totalItems;         cart.append(document.createElement('p').innerHTML = 'Goods: ' + totalGoods + '. Price: ' + totalPrice);     } else {         // если в корзине пусто         cart.innerHTML = 'empty';     } } // открываем корзину document.getElementById('open').addEventListener('click', openCart);  // очищаем корзину document.getElementById('clear').addEventListener('click', () => {     localStorage.removeItem('cart');     cart.innerHTML = 'сleared'; }); 

Результат выглядит так:

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

При нажатии кнопки «Cart» данные из localStorage выводятся в таблицу, подсчитывается общее количество товаров и их стоимость.

Codepen

Github

Благодарю за внимание.

ссылка на оригинал статьи https://habr.com/ru/post/496348/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *