Ограничения и особенности сериализации

Общая модель хранения данных

localForage строится вокруг унифицированного API поверх различных механизмов хранения браузера: IndexedDB, WebSQL и localStorage. При этом ключевым аспектом является то, что все значения проходят через слой сериализации и десериализации, поведение которого зависит от выбранного драйвера.

Главная особенность архитектуры заключается в попытке абстрагировать различия между хранилищами, сохраняя единый интерфейс setItem / getItem. Однако именно на этапе преобразования данных в формат, пригодный для хранения, возникают основные ограничения.


Сериализация в IndexedDB: структурированное клонирование

При использовании IndexedDB применяется алгоритм structured clone, который поддерживает более богатые типы данных по сравнению с JSON.

Поддерживаемые типы:

  • Object и Array
  • Date
  • RegExp
  • Blob
  • File
  • ArrayBuffer
  • TypedArray (Uint8Array, Float32Array и др.)
  • Map и Set (в современных реализациях браузеров)

Особенность structured clone заключается в том, что данные не превращаются в строку, а копируются в бинарное представление, пригодное для хранения внутри базы.

Ключевая характеристика:

  • Сохранение типов данных без ручной сериализации
  • Отсутствие необходимости использовать JSON.stringify
  • Поддержка бинарных данных без кодирования в Base64

Однако даже в этом случае сохраняются ограничения:

  • невозможность сериализации функций
  • невозможность сериализации DOM-узлов
  • невозможность сохранения ссылок на объекты среды выполнения (window, document)
  • ограничение на графы с неразрешимыми циклическими структурами в некоторых браузерах

Ограничения localStorage-драйвера

При использовании localStorage данные всегда преобразуются в строки. localForage в этом случае использует JSON-сериализацию как промежуточный слой.

Последствия такого подхода:

  • Потеря типов данных
  • Преобразование всех значений в строковое представление
  • Ограниченная поддержка сложных структур

Типичные преобразования:

  • Date → строка ISO
  • ArrayBuffer → невозможен без ручного преобразования
  • undefined → теряется (ключ может быть удалён или сериализован некорректно)
  • NaN, Infinity → становятся null или строками в зависимости от реализации JSON

Также localStorage накладывает собственные ограничения:

  • синхронный API, блокирующий основной поток
  • ограничение по объёму (обычно 5–10 МБ на домен)
  • возможные исключения при переполнении

Потеря и восстановление типов

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

Примеры поведения:

  • При IndexedDB:

    • Date возвращается как Date
    • Blob сохраняется как Blob
    • ArrayBuffer сохраняется в исходном виде
  • При localStorage:

    • всё восстанавливается через JSON.parse
    • сложные типы превращаются в plain object или строку

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


Ограничения JSON-слоя

Даже при работе через IndexedDB localForage может использовать JSON как fallback-слой в определённых конфигурациях. В этом случае проявляются классические ограничения JSON:

  • отсутствие поддержки undefined
  • невозможность сериализации функций
  • потеря Symbol
  • игнорирование свойств в прототипе
  • невозможность хранения циклических ссылок без предварительной обработки

Циклические структуры требуют предварительного разрыва ссылок или использования специализированных структур данных.


Работа с бинарными данными

localForage поддерживает хранение бинарных данных, но поведение зависит от драйвера.

IndexedDB

  • прямое хранение ArrayBuffer
  • поддержка TypedArray без преобразований
  • поддержка Blob и File

localStorage

  • невозможность хранения бинарных данных напрямую
  • необходимость кодирования (Base64 или аналог)
  • увеличение размера данных примерно на 30–35%

Это приводит к существенной разнице в производительности и объёмах хранимых данных.


Проблемы совместимости между браузерами

Несмотря на стандартизацию IndexedDB, поведение structured clone имеет различия:

  • различия в поддержке Map и Set в старых браузерах
  • частичная несовместимость Blob в устаревших реализациях
  • различия в обработке ошибок сериализации

localForage скрывает часть этих различий, но не устраняет их полностью, особенно при использовании нестабильных или устаревших окружений.


Ограничения размера и производительности

Каждый драйвер накладывает собственные ограничения:

  • IndexedDB: ограничение зависит от дискового пространства и политики браузера
  • localStorage: фиксированный лимит на домен
  • WebSQL (устаревший): ограничение зависит от реализации SQLite

Дополнительные аспекты:

  • операции записи могут быть асинхронными и с задержкой
  • массовые записи приводят к блокировкам транзакций в IndexedDB
  • большие объекты увеличивают время сериализации и десериализации

Потеря семантики при миграции данных

При переключении драйвера или миграции между версиями хранилища возникают проблемы:

  • несовместимость форматов сериализации
  • различие в поддержке типов
  • необходимость ручной трансформации данных

Особенно критично это при переходе:

  • localStorage → IndexedDB
  • WebSQL → IndexedDB

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


Ключи и структура данных

localForage использует ключ-значение модель без встроенной схемы.

Ограничения:

  • отсутствие индексов по полям объектов
  • невозможность частичного запроса данных
  • отсутствие встроенной фильтрации

Это влияет на архитектуру данных, требуя хранения уже готовых структур, а не нормализованных моделей.


Поведение при ошибках сериализации

Ошибки сериализации могут возникать в следующих случаях:

  • наличие неподдерживаемых типов
  • превышение квоты хранения
  • повреждение структуры данных
  • несовместимость данных между драйверами

Типичное поведение:

  • отклонение Promise при setItem
  • частичная потеря данных при неатомарных операциях
  • fallback на другой драйвер при инициализации

Особенности кэширования и копирования

Каждая операция чтения и записи фактически создает копию объекта:

  • отсутствие ссылочной семантики между сохранённым и текущим объектом
  • изменение объекта после сохранения не влияет на хранилище
  • повторное чтение всегда возвращает новую копию

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