От автора: хранение данных и управление ими в браузере — также известное как хранилище на стороне клиента — полезно, когда не нужно или нецелесообразно отправлять их на веб-сервер.
Ситуации для хранения и обработки данных в браузере включают:
сохранение состояния клиентского приложения, такого как текущий экран, введенные данные, пользовательские настройки и т. д.
утилиты, которые обращаются к локальным данным или файлам и имеют строгие требования к конфиденциальности
прогрессивные веб-приложения (PWA), которые работают в автономном режиме
Вот десять вариантов хранения данных браузера:
1. JavaScript переменные
2. Хранилище узлов DOM
3. Web хранилище (localStorage и sessionStorage)
4. IndexedDB
5. Cache API (не используйте AppCache!)
6. API доступа к файловой системе
7. API записей файлов и каталогов
8. cookies
9. window.name
10. WebSQL
В этой статье рассматриваются эти десять различных способов хранения данных в браузере, рассматриваются их ограничения, плюсы и минусы, а также наилучшее использование каждого метода. Прежде чем мы перейдем к вариантам, сделаем небольшое примечание о сохранении данных.
Сохранение данных
Как правило, данные, которые сохраняются, будут:
Постоянные (persistent): они остаются до тех пор, пока ваш код не решит удалить их, или
изменяемые (volatile) : они остаются до завершения сеанса браузера, обычно, когда пользователь закрывает вкладку
Постоянные данные могут быть заблокированы или удалены пользователем, операционной системой, браузером или плагинами в любой момент. Браузер может принять решение об удалении более старых или больших элементов по мере приближения к граничной емкости, выделенной для этого типа хранилища.
Браузеры также записывают состояние страницы. Вы можете уйти с сайта и кликнуть назад или закрыть и снова открыть вкладку; страница должна выглядеть идентично. Переменные и данные, доступные только для сеанса, по-прежнему доступны.
1. Переменные JavaScript
размер — нет строгих ограничений, но при заполнении памяти может произойти замедление работы браузера или сбои
скорость чтения / записи — самый быстрый вариант
сохранность — плохая: данные стираются при обновлении браузера
Сохранение состояния в переменных JavaScript — самый быстрый и простой вариант. Я уверен, что вам не нужен пример, но …
const a = 1, b = 'two', state = { msg: 'Hello', name: 'Craig' };
Преимущества:
легко использовать
быстрота
нет необходимости в сериализации или десериализации
Недостатки:
ненадежность: обновление или закрытие вкладки стирает все
сторонние скрипты могут исследовать или перезаписывать глобальные значения (window). Вы уже используете переменные. Вы можете рассмотреть возможность сохранения состояния переменной при выгрузке страницы.
2. Хранилище узлов DOM
размер — нет строгих ограничений, но не идеально для большого количества данных
скорость чтения / записи — Быстрый
сохранность — плохая: данные могут быть удалены другими скриптами или обновлением
Большинство элементов DOM на странице или в памяти могут хранить значения в именованных атрибутах. Безопаснее использовать имена атрибутов с префиксом data-:
атрибут никогда не будет иметь связанных функций HTML
Вы можете получить доступ к значениям с помощью свойства dataset или через методы .setAttribute() и .getAttribute().
Значения хранятся в виде строк, поэтому может потребоваться сериализация и десериализация. Например:
// locate <main> element const main = document.querySelector('main'); // store values main.dataset.value1 = 1; main.dataset.state = JSON.stringify({ a:1, b:2 }); // retreive values console.log( main.dataset.value1 ); // "1" console.log( JSON.parse(main.dataset.state).a ); // 1
Преимущества:
вы можете определять значения в JavaScript или HTML, например <main data-value1=»1″>
полезно для хранения состояния конкретного компонента
DOM работает быстро! (вопреки распространенному мнению)
Недостатки:
ненадёжно: обновление или закрытие вкладки стирает значения
только строки: требуется сериализация и десериализация
большой DOM влияет на производительность
сторонние скрипты могут исследовать или перезаписывать значения
Хранилище узлов DOM работает медленнее, чем переменные. Используйте его экономно в ситуациях, когда удобно хранить состояние компонента в HTML.
3. Web хранилище (localStorage и sessionStorage)
размер — 5 МБ на домен
скорость чтения / записи — синхронная работа: может быть медленной
сохранность — данные остаются до тех пор, пока не будут удалены
Веб-хранилище предоставляет два похожих API для определения пар имя/значение. Используйте:
window.localStorage для хранения постоянных данных и
window.sessionStorage для сохранения данных только сеанса, пока вкладка браузера остается открытой
Храните или обновляйте именованные элементы с помощью .setItem():
localStorage.setItem('value1', 123); localStorage.setItem('value2', 'abc'); localStorage.setItem('state', JSON.stringify({ a:1, b:2, c:3 }));
Получайте их с помощью .getItem():
const state = JSON.parse( localStorage.getItem('state') );
И удалите их с помощью .removeItem():
localStorage.removeItem('state')
Другие свойства и методы включают:
.length: количество хранимых элементов
.key(N): имя N-го ключа
.clear(): удаление всех сохраненных элементов
Изменение любого значения вызывает событие хранения в других вкладках / окнах браузера, подключенных к тому же домену. Ваше приложение может ответить соответствующим образом:
window.addEventListener('storage', s => { console.log(`item changed: ${ s.key }`); console.log(`from value : ${ s.oldValue }`); console.log(`to new value: ${ s.newValue }`); });
Преимущества:
простой API (пары имя / значение)
параметры сеанса и постоянного хранилища
хорошая поддержка браузера
Недостатки:
Только строки: требуется сериализация и десериализация
неструктурированные данные без транзакций, индексации или поиска
синхронный доступ повлияет на производительность больших наборов данных
Веб-хранилище идеально подходит для простых, небольших и разовых значений. Оно менее практично для хранения больших объемов структурированной информации, но вы можете избежать проблем с производительностью, записывая данные при выгрузке страницы.
4. IndexedDB
размер — зависит от устройства. Не менее 1 ГБ, но может составлять до 60% оставшегося дискового пространства
скорость чтения / записи — быстрый
сохранность — данные остаются до тех пор, пока не будут удалены
IndexedDB предлагает низкоуровневый API, похожий на NoSQL, для хранения больших объемов данных. Хранилище можно индексировать, обновлять с помощью транзакций и выполнять поиск с помощью асинхронных методов.
IndexedDB API сложен и требует некоторого манипулирования событиями. Следующая функция открывает соединение с базой данных при передаче имени, номера версии и дополнительной функции обновления (вызываемой при изменении номера версии):
// connect function dbConnect(dbName, version, upgrade) { return new Promise((resolve, reject) => { const request = indexedDB.open(dbName, version); request.onsuccess = e => { resolve(e.target.result); }; request.onerror = e => { console.error(`indexedDB error: ${ e.target.errorCode }`); }; request.onupgradeneeded = upgrade; }); }
Следующий код подключается к базе данных myDB и инициализирует хранилище объектов todo (аналогично таблице SQL или MongoDB). Затем он определяет автоматически увеличивающийся ключ с именем id:
(async () => { const db = await dbConnect('myDB', 1.0, e => { db = e.target.result; const store = db.createObjectStore('todo', { keyPath: 'id', autoIncrement: true }); }) })();
Как только соединение будет готово, вы можете с помощью .add добавить новые элементы данных в транзакцию:
db.transaction(['todo'], 'readwrite') .objectStore('todo') .add({ task: 'do something' }) .onsuccess = () => console.log( 'added' );
И вы можете получить значения, например, первый элемент:
db.transaction(['todo'], 'readonly') .objectStore('todo') .get(1) .onsuccess = data => console.log( data.target.result ); // { id: 1, task: 'do something' }
Преимущества:
гибкое хранилище данных с самым большим пространством
надежные транзакции, возможности индексации и поиска
хорошая поддержка браузера
Недостатки:
сложный обратный вызов и API на основе событий
IndexedDB — лучший вариант для надежного хранения больших объемов данных, но вам может понадобиться библиотека-оболочка, такая как idb, Dexie.js или JsStore.
5. Cache API
размер — зависит от устройства, но Safari ограничивает каждый домен до 50 МБ
скорость чтения / записи — быстрый
сохранность — данные остаются до очистки или через две недели в Safari
Cache API обеспечивает хранение для пар HTTP запросов и ответов. Вы можете создать любое количество именованных кэшей для хранения любого количества элементов данных сети.
API обычно используется в сервис-воркерах для кэширования сетевых ответов для прогрессивных веб-приложений. Когда устройство отключается от сети, кэшированные ресурсы могут быть повторно использованы, чтобы веб-приложение могло работать в автономном режиме. Следующий код сохраняет сетевой ответ в кэше с именем myCache:
// cache name const cacheName = 'myCache'; (async () => { // cache network response const stored = await cacheStore('/service.json') ); console.log(stored ? 'stored OK' : 'store failed'); })(); // store request async function cacheStore( url ) { try { // open cache const cache = await caches.open( cacheName ); // fetch and store response await cache.add( url ); return true; } catch(err) { return undefined; // store failed } }
Аналогичная функция может получить элемент из кеша. В этом примере она возвращает основной текст ответа:
(async () => { // fetch text from cached response const text = await cacheGet('/service.json') ); console.log( text ); })(); async function cacheGet( url ) { try { const // open cache cache = await caches.open( cacheName ), // fetch stored response resp = await cache.match(url); // return body text return await resp.text(); } catch(err) { return undefined; // cache get failed } }
Преимущества:
хранит любой сетевой ответ
может улучшить производительность веб-приложений
позволяет веб-приложению работать в автономном режиме
современный API
Недостатки:
не практично для хранения состояния приложения
возможно менее полезно за пределами прогрессивных веб-приложений
Apple недоброжелательно относится к PWA и Cache API
Cache API — лучший вариант для хранения файлов и данных, полученных из сети. Вы, вероятно, могли бы использовать его для хранения состояния приложения, но он не предназначен для этой цели, и есть варианты получше.
5.5 AppCache
AppCache был предшественником Cache API. Это не то решение для хранения, которое вы ищете. Здесь ничего нет. Пожалуйста, двигайтесь дальше.
6. API доступа к файловой системе
размер — зависит от оставшегося места на диске
скорость чтения / записи — зависит от файловой системы
сохранность — данные остаются до тех пор, пока не будут удалены
API доступа к файловой системе позволяет браузеру читать, записывать, изменять и удалять файлы из локальной файловой системы. Браузеры работают в изолированной среде, поэтому пользователь должен предоставить разрешение на определенный файл или каталог. Чтобы веб-приложение могло читать или записывать данные, как настольное приложение, используют FileSystemHandle.
Следующая функция сохраняет объект Blob в локальный файл:
async function save( blob ) { // create handle to a local file chosen by the user const handle = await window.showSaveFilePicker(); // create writable stream const stream = await handle.createWritable(); // write the data await stream.write(blob); // save and close the file await stream.close(); }
Преимущества:
веб-приложения могут безопасно читать и записывать в локальную файловую систему
меньше необходимости загружать файлы или обрабатывать данные на сервере
отличная функция для прогрессивных веб-приложений
Недостатки:
минимальная поддержка браузера (только Chrome)
API может измениться
Этот вариант хранения для меня очень интересен, но вам придется подождать пару лет, прежде чем он станет жизнеспособным для производственного использования.
7. API записей файлов и каталогов
размер — зависит от оставшегося места на диске
скорость чтения / записи — неизвестный
сохранность — данные остаются до тех пор, пока не будут удалены
API записей файлов и каталогов предоставляют песочницы файловой системы доступной для домена, которые могут создавать, писать, читать и удалять каталоги и файлов.
Преимущества:
может иметь несколько интересных применений
Недостатки:
нестандартные, несовместимость между реализациями и поведение могут измениться.
MDN прямо заявляет: не используйте это на производственных сайтах . Поддержка будет в лучшем случае через несколько лет.
8. Файлы cookie
размер — 80 КБ на домен (20 файлов cookie размером до 4 КБ в каждом)
скорость чтения / записи — быстрый
сохранность — хорошая: данные остаются до тех пор, пока они не будут удалены или не истечет время их жизни
Файлы cookie — это данные, относящиеся к домену. Они имеют репутацию что они отслеживают людей, но они необходимы для любой системы, которая должна поддерживать состояние сервера, например, для входа в систему. В отличие от других механизмов хранения, файлы cookie (обычно) передаются между браузером и сервером при каждом HTTP-запросе и ответе. Оба устройства могут проверять, изменять и удалять данные cookie.
document.cookie устанавливает значения cookie в клиентском JavaScript. Вы должны определить строку с именем и значением, разделенными символом равенства (=). Например:
document.cookie = 'cookie1=123'; document.cookie = 'anothercookie=abc';
Значения не должны содержать запятых, точек с запятой или пробелов, поэтому может потребоваться encodeURIComponent():
document.cookie = `hello=${ encodeURIComponent('Hello, everyone!') }`;
К дополнительным настройкам файлов cookie можно добавить разделители через точку с запятой, в том числе:
;domain= : если не установлено, то cookie доступен только в текущем домене URL. Использование ;path=mysite.com разрешило бы на любом поддомене mysite.com.
;path= : если не установлено, cookie доступен только в текущем и дочерних доменах. Установите, ;path=/ чтобы разрешить любой путь в домену.
;max-age= : время истечения срока действия cookie в секундах, например ;max-age=60.
;expires= : дата истечения срока действия cookie, например ;expires=Thu, 04 July 2021 10:34:38 UTC(используйте date.toUTCString() для соответствующего форматирования).
;secure: cookie будет передаваться только по HTTPS.
;HTTPOnly: делает файлы cookie недоступными для клиентского JavaScript.
;samesite=: определяет, может ли другой домен получить доступ к cookie. Может иметь значение lax (по умолчанию, использует файл cookie для текущего домена), strict (останавливает отправку исходного файла cookie при переходе по ссылке из другого домена) или none (без ограничений).
Пример: установить файл cookie, срок действия которого истекает через 10 минут и доступен по любому пути в текущем домене:
const state = { a:1, b:2, c:3 }; document.cookie = `state=${ encodeURIComponent(JSON.stringify(state)) }; path=/; max=age=600`;
document.cookie возвращает строку, содержащую каждую пару имени и значения, разделенную точкой с запятой. Например:
console.log( document.cookie ); // "cookie1=123; anothercookie=abc; hello=Hello%2C%20everyone!; state=%7B%22a%22%3A1%2C%22b%22%3A2%2C%22c%22%3A3%7D"
Функция ниже анализирует строку и преобразует ее в объект, содержащий пары имя-значение. Например:
const cookie = cookieParser(); state = cookie.state; console.log( state ); // { a:1, b:2, c:3 } // parse cookie values function cookieParser() { const nameValue = {}; document.cookie .split('; ') .map(nv => { nv = nv.split('='); if (nv[0]) { let v = decodeURIComponent( nv[1] || '' ); try { v = JSON.parse(v); } catch(e){} nameValue[ nv[0] ] = v; } }) return nameValue; }
Преимущества:
надежный способ сохранить состояние между клиентом и сервером
ограничен доменом
автоматический контроль истечения срока действия с помощью max-age (секунд) или Expires (дата)
используется в текущем сеансе по умолчанию (установите дату истечения срока, чтобы данные сохранялись после обновления страницы и закрытия вкладки)
Недостатки:
файлы cookie часто блокируются браузерами и плагинами (обычно они конвертируются в файлы cookie сеанса, поэтому сайты продолжают работать)
плохая реализация JavaScript (лучше всего создать свой собственный обработчик файлов cookie или выбрать библиотеку, такую как js-cookie )
только строки (требуется сериализация и десериализация)
ограниченное место для хранения
cookie могут быть перехвачены сторонними скриптами, если вы не ограничили доступ
обвиняются в нарушении конфиденциальности (региональное законодательство может требовать, чтобы вы показывали предупреждение о файлах cookie)
данные cookie добавляются к каждому HTTP-запросу и ответу, что может повлиять на производительность (для хранение 50 КБ данных cookie, а затем запроса десяти 1-байтовых файлов потребуется один мегабайт)
Избегайте файлов cookie, используйте их если нет реальной альтернативы.
9. window.name
размер — варьируется, но должно быть несколько мегабайт
скорость чтения / записи — быстрый
сохранность — данные сеанса остаются до закрытия вкладки
Свойство window.name устанавливает и получает имя контекста активного окна. Вы можете установить одно строковое значение, которое будет сохраняться между обновлениями браузера. Например:
let state = { a:1, b:2, c:3 }; window.name = JSON.stringify( state );
Исследуйте значение, используя:
state = JSON.parse( window.name ); console.log( state.b ); // 2
Преимущества:
легко использовать
может использоваться только для данных сеанса
Недостатки:
Только строки: требуется сериализация и десериализация
страницы в других доменах могут читать, изменять или удалять данные (никогда не используйте их для конфиденциальной информации)
Window.name не предназначен для хранения данных. Это хак, и есть варианты получше.
10. WebSQL
размер — 5 МБ на домен
скорость чтения / записи — медленная
сохранность — данные остаются до тех пор, пока не будут удалены
WebSQL был попыткой перенести в браузер хранилище баз данных, подобное SQL. Пример кода:
// create DB (name, version, description, size in bytes) const db = openDatabase('todo', '1.0', 'my to-do list', 1024 * 1024); // create table and insert first item db.transaction( t => { t.executeSql('CREATE TABLE task (id unique, name)'); t.executeSql('INSERT INTO task (id,name) VALUES (1, "wash cat")'); }); // output array of all items db.transaction( t => { t.executeSql( "SELECT * FROM task", [], (t, results) => { console.log(results.rows); } ); });
Chrome и некоторые версии Safari поддерживают эту технологию, но против нее выступили Mozilla и Microsoft в пользу IndexedDB.
Преимущества:
разработан для надежного хранения и доступа к данным на стороне клиента
знакомый синтаксис SQL, часто используемый серверными разработчиками
Недостатки:
ограниченная поддержка браузеров
несогласованный синтаксис SQL в браузерах
асинхронный, но медленный API на основе обратного вызова
плохая работа
Не используйте WebSQL! Он не был жизнеспособным вариантом с тех пор, как устарела его спецификация в 2010 году.
Тщательная проверка хранилища
API хранилища может исследовать пространство , доступное для веб-хранилища, IndexedDB, и Cache API. Все браузеры, кроме Safari и IE, поддерживают это API, которое предлагает метод .estimate() для вычисления значений quota (пространства, доступного для домена) и usage (пространства, уже используемого). Например:
(async () => { if (!navigator.storage) return; const storage = await navigator.storage.estimate(); console.log(`bytes allocated : ${ storage.quota }`); console.log(`bytes in use : ${ storage.usage }`); const pcUsed = Math.round((storage.usage / storage.quota) * 100); console.log(`storage used : ${ pcUsed }%`); const mbRemain = Math.floor((storage.quota - storage.usage) / 1024 / 1024); console.log(`storage remaining: ${ mbRemain } MB`); })();
Доступны еще два асинхронных метода:
.persist(): возвращает true если у сайта есть разрешение на хранение постоянных данных, и
.persisted(): возвращает true если сайт уже сохранил постоянные данные
Панель «Приложение» в инструментах разработчика браузера ( в Firefox называется « Хранилище» ) позволяет просматривать, изменять и очищать localStorage, sessionStorage, IndexedDB, WebSQL, файлы cookie и кеш хранилища.
Вы также можете проверить данные cookie, отправленные в заголовках HTTP-запроса и ответа, кликнув любой элемент на панели Network инструментов разработчика.
Заключение
Ни одно из этих решений для хранения не является идеальным, и вам нужно будет внедрить несколько решений в сложное веб-приложение. Это означает изучение дополнительных API. Но иметь выбор — это хорошо — конечно, при условии, что вы можете подобрать подходящий вариант!
Автор: Craig Buckler
Источник: www.sitepoint.com
Редакция: Команда webformyself.
Читайте нас в Telegram, VK, Яндекс.Дзен