Бинарные данные: ArrayBuffer

Работа с бинарными данными в браузере требует понимания того, как JavaScript представляет и передаёт низкоуровневые структуры памяти. В контексте клиентского хранения данных важным становится использование ArrayBuffer, TypedArray и связанных структур, поскольку они позволяют сохранять изображения, аудиофрагменты, криптографические ключи и произвольные байтовые последовательности без преобразования в строковый формат.

localForage использует единый API поверх различных механизмов хранения (IndexedDB, WebSQL, localStorage), и именно работа со структурированными данными делает возможным сохранение бинарных буферов без ручной сериализации.


ArrayBuffer как базовая модель памяти

ArrayBuffer представляет собой фиксированную область памяти, содержащую «сырые» байты. Он не интерпретирует данные самостоятельно, а лишь предоставляет контейнер, к которому можно подключать представления:

  • Uint8Array — массив байтов без знака
  • Int16Array — 16-битные целые
  • Float32Array — числа с плавающей точкой
  • DataView — гибкий доступ к байтам с произвольной интерпретацией

Ключевая особенность заключается в том, что ArrayBuffer не содержит логики преобразования — он лишь хранит последовательность байтов фиксированной длины.


Поддержка бинарных данных в localForage

Внутри localForage работа с бинарными структурами опирается на Structured Clone Algorithm, используемый IndexedDB. Этот механизм позволяет сохранять:

  • ArrayBuffer
  • TypedArray
  • Blob
  • File

без необходимости преобразования в JSON или строку.

В отличие от localStorage, где всё приводится к строке, здесь сохраняется оригинальная структура данных.


Запись ArrayBuffer в хранилище

При сохранении бинарных данных используется стандартный асинхронный API:

const buffer = new ArrayBuffer(8);
const view = new Uint8Array(buffer);

view[0] = 255;
view[1] = 128;

await localforage.setItem("binary-key", buffer);

В момент вызова происходит передача объекта в IndexedDB, где структура клонируется без потери байтовой информации.


Чтение бинарных данных

Извлечение данных возвращает тот же тип объекта, который был сохранён:

const result = await localforage.getItem("binary-key");

const view = new Uint8Array(result);
console.log(view[0]);

Тип возвращаемого значения зависит от сохранённого объекта. Если был сохранён ArrayBuffer, на выходе также будет ArrayBuffer.


TypedArray и сохранение структурированных байтов

TypedArray часто используется как промежуточное представление перед сохранением:

const pixels = new Uint8Array([10, 20, 30, 40, 50]);

await localforage.setItem("pixels", pixels);

При извлечении данные сохраняют свою форму:

const pixels = await localforage.getItem("pixels");

console.log(pixels instanceof Uint8Array);

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


Blob и ArrayBuffer: различия хранения

Blob и ArrayBuffer часто используются совместно, но имеют различную семантику:

  • ArrayBuffer — низкоуровневые байты
  • Blob — неизменяемый объект файла с MIME-типом

Сохранение Blob:

const blob = new Blob(["hello"], { type: "text/plain" });

await localforage.setItem("file", blob);

При извлечении объект сохраняет файловую структуру, однако при необходимости низкоуровневой обработки часто выполняется преобразование:

const blob = await localforage.getItem("file");
const buffer = await blob.arrayBuffer();

Внутренние механизмы сериализации

В основе хранения лежит алгоритм структурного клонирования:

  • копируются байтовые представления
  • сохраняется тип объекта
  • восстанавливаются прототипы TypedArray

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


Ограничения и особенности работы

1. Клонирование, а не ссылка

Каждая операция записи создаёт независимую копию данных. Изменения исходного ArrayBuffer после сохранения не влияют на сохранённое значение.

2. Размер данных

IndexedDB допускает значительные объёмы данных, однако производительность зависит от:

  • размера буфера
  • частоты операций записи
  • фрагментации хранилища

3. Переносимость типов

Не все реализации одинаково восстанавливают сложные типы TypedArray. В некоторых окружениях восстановление может приводить к упрощённому виду (Uint8Array вместо специализированного типа).


Конвертация между форматами

ArrayBuffer → Base64

Иногда требуется преобразование для передачи через API:

function bufferToBase64(buffer) {
  let binary = "";
  const bytes = new Uint8Array(buffer);

  for (let i = 0; i < bytes.byteLength; i++) {
    binary += String.fromCharCode(bytes[i]);
  }

  return btoa(binary);
}

Base64 → ArrayBuffer

function base64ToBuffer(base64) {
  const binary = atob(base64);
  const len = binary.length;

  const buffer = new ArrayBuffer(len);
  const view = new Uint8Array(buffer);

  for (let i = 0; i < len; i++) {
    view[i] = binary.charCodeAt(i);
  }

  return buffer;
}

Несмотря на универсальность Base64, его использование увеличивает размер данных примерно на 33%, тогда как ArrayBuffer сохраняет компактное бинарное представление.


Практика хранения изображений

Бинарные данные часто применяются для локального кэширования изображений:

const response = await fetch("/image.png");
const buffer = await response.arrayBuffer();

await localforage.setItem("image", buffer);

При последующем использовании:

const buffer = await localforage.getItem("image");
const blob = new Blob([buffer], { type: "image/png" });

const url = URL.createObjectURL(blob);

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


Особенности производительности

Работа с ArrayBuffer в связке с IndexedDB имеет следующие характеристики:

  • запись выполняется асинхронно и не блокирует поток UI
  • большие буферы требуют времени на клонирование
  • чтение быстрее записи при повторном доступе (кэширование браузера)

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


Совместимость и fallback-механизмы

localForage автоматически выбирает backend:

  • IndexedDB — основной вариант для бинарных данных
  • WebSQL — устаревшая альтернатива
  • localStorage — используется только для строк

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


Использование с криптографическими данными

ArrayBuffer часто применяется для хранения:

  • ключей шифрования
  • хэшей
  • случайных значений (entropy)
const key = crypto.getRandomValues(new Uint8Array(32));

await localforage.setItem("key", key.buffer);

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


Поток обработки бинарных данных

Типичный поток взаимодействия включает этапы:

  1. получение данных (fetch, File API, Canvas)
  2. преобразование в ArrayBuffer
  3. сохранение через localForage
  4. восстановление
  5. обёртка в Blob или TypedArray
  6. использование в API браузера

Каждый этап опирается на сохранение неизменной бинарной структуры, что делает возможным построение офлайн-кэша и локальных медиа-хранилищ без промежуточных форматов.