Хранение данных и управление ими в браузере — также известное как хранилище на стороне клиента — полезно, когда не нужно или нецелесообразно отправлять их на веб-сервер.
Ситуации для хранения и обработки данных в браузере включают:
- сохранение состояния клиентского приложения, такого как текущий экран, введенные данные, пользовательские настройки и т. д.
- утилиты, которые получают доступ к локальным данным или файлам и имеют строгие требования к конфиденциальности
- прогрессивные веб-приложения (PWA), которые работают в автономном режиме.
Вот десять вариантов хранения данных браузера:
- Переменные JavaScript
- Хранилище узлов DOM
- Интернет-хранилище( localStorageи sessionStorage)
- IndexedDB
- Cache API(не используйте AppCache !)
- API доступа к файловой системе
- API записей файлов и каталогов
- Cookies
- Name
- WebSQL
В этой статье исследуются эти десять различных способов хранения данных в браузере, рассматриваются их ограничения, плюсы и минусы, а также наилучшее использование каждого метода.
Прежде чем мы перейдем к вариантам, сделаем небольшое примечание о сохранении данных…
Сохранение данных
Как правило, данные, которые вы храните, будут:
- постоянный: он остается до тех пор, пока ваш код не решит удалить его, или
- volatile: он остается до завершения сеанса браузера, обычно, когда пользователь закрывает вкладку
Реальность более тонкая.
Постоянные данные могут быть заблокированы или удалены пользователем, операционной системой, браузером или плагинами в любой момент. Браузер может принять решение об удалении более старых или крупных элементов по мере приближения к емкости, выделенной для этого типа хранилища.
Браузеры также записывают состояние страницы. Вы можете уйти с сайта и щелкнуть назад или закрыть и снова открыть вкладку; страница должна выглядеть идентично. Переменные и данные, считающиеся доступными только для сеанса, по-прежнему доступны.
1. Переменные JavaScript
метрика | комментарий |
вместимость | нет строгих ограничений, но при заполнении памяти может произойти замедление работы браузера или сбои |
скорость чтения / записи | самый быстрый вариант |
упорство | плохой: данные стираются при обновлении браузера |
Сохранение состояния в переменных JavaScript — самый быстрый и простой вариант. Я уверен, что вам не нужен пример, но…
const a = 1, b = 'two', state = { msg: 'Hello', name: 'Craig' };
Преимущества:
- легко использовать
- быстрый
- нет необходимости в сериализации или десериализации
Недостатки:
- хрупкий: обновление или закрытие вкладки стирает все
- сторонние скрипты могут проверять или перезаписывать значения global ( 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 работает быстро! (вопреки распространенному мнению)
Недостатки:
- хрупкий: обновление или закрытие вкладки стирает все (если значение не отображается на сервере в HTML)
- Только строки: требуется сериализация и десериализация
- более крупный DOM влияет на производительность
- сторонние скрипты могут проверять или перезаписывать значения
Хранилище узлов DOM работает медленнее, чем переменные. Используйте его экономно в ситуациях, когда удобно хранить состояние компонента в HTML.
3. Web Storage (localStorage
and sessionStorage
)
метрика | комментарий |
вместимость | 5 МБ на домен |
скорость чтения / записи | синхронная работа: может быть медленной |
упорство | данные остаются до тех пор, пока не будут удалены |
Веб-хранилище предоставляет два похожих API для определения пар имя / значение. Использовать:
- 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, для хранения больших объемов данных. Хранилище можно индексировать, обновлять с помощью транзакций и выполнять поиск с помощью асинхронных методов.
API IndexedDB сложен и требует некоторого манипулирования событиями. Следующая функция открывает соединение с базой данных при передаче имени, номера версии и дополнительной функции обновления (вызываемой при изменении номера версии):
// 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 }); }) })();
Как только dbсоединение будет готово, вы можете.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. API кеширования
метрика | комментарий |
вместимость | зависит от устройства, но Safari ограничивает каждый домен до 50 МБ |
скорость чтения / записи | быстрый |
упорство | данные остаются до очистки или через две недели в Safari |
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 — лучший вариант для хранения файлов и данных, полученных из сети. Вероятно, вы могли бы использовать его для хранения состояния приложения, но он не предназначен для этой цели, и есть варианты получше.
AppCache был несуществующим предшественником Cache API. Это не то решение для хранения, которое вы ищете. Здесь ничего нет. Пожалуйста, двигайтесь дальше.
6. API доступа к файловой системе
метрика | комментарий |
вместимость | зависит от оставшегося места на диске |
скорость чтения / записи | зависит от файловой системы |
упорство | данные остаются до тех пор, пока не будут удалены |
File System Access 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 хранения может исследовать пространство, доступное для веб — хранилище, индексированной, и 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-запроса и ответа, щелкнув любой элемент на панели » Сеть» инструментов разработчика.
Storage Smorgasbord
Ни одно из этих решений для хранения не является идеальным, и вам нужно будет внедрить несколько в сложное веб-приложение. Это означает изучение дополнительных API. Но иметь выбор в каждой ситуации — это хорошо — конечно, при условии, что вы можете выбрать подходящий вариант!
Спасибо!