ArrayBuffer и Typed Arrays
ArrayBuffer и Typed Arrays
Технический фундамент бинарного представления
При работе с бинарными данными нужно разделять три уровня:
ArrayBufferкак сырая область памяти в байтах;TypedArrayкак типизированное представление этой памяти;DataViewкак низкоуровневый доступ по смещениям и типам.
От выбора представления зависит корректность чтения формата и интерпретация байтов.
Почему эта тема нужна в JavaScript
Обычно в JS ты работаешь со строками, объектами и обычными массивами. Но для файлов, аудио, изображений, сетевых пакетов и Web API часто нужны бинарные данные. Для этого есть ArrayBuffer и типизированные массивы (Typed Arrays).
Ключевой момент: ArrayBuffer это "сырой" блок памяти, а TypedArray это удобный способ читать и писать в этот блок как числа конкретного типа.
Проверь себя: почему для изображения в байтах неудобно использовать обычный массив number[]?
ArrayBuffer: контейнер памяти
ArrayBuffer задает размер буфера в байтах, но сам по себе не дает методов чтения/записи по индексам.
const buffer = new ArrayBuffer(8);
console.log(buffer.byteLength); // 8
Смотри, что важно: это только "память", без интерпретации данных.
TypedArray: представление данных
Типизированные массивы дают доступ к буферу как к массиву фиксированного числового типа.
const buffer = new ArrayBuffer(8);
const bytes = new Uint8Array(buffer);
bytes[0] = 255;
bytes[1] = 10;
console.log(bytes[0]); // 255
console.log(bytes.length); // 8
Популярные типы:
Uint8Array- беззнаковые 8-битные числа (0..255);Int16Array- знаковые 16-битные;Float32Array- числа с плавающей точкой.
Один буфер, несколько представлений
Можно создать несколько представлений над одним буфером.
const buffer = new ArrayBuffer(4);
const bytes = new Uint8Array(buffer);
const words = new Uint16Array(buffer);
bytes[0] = 1;
bytes[1] = 0;
console.log(words[0]); // зависит от порядка байтов, обычно 1
Здесь часто путаются: разные представления читают те же байты по-разному.
Проверь себя: почему изменение в bytes сразу отражается в words?
DataView для гибкого чтения
DataView позволяет вручную читать/писать разные типы по смещениям.
const buffer = new ArrayBuffer(4);
const view = new DataView(buffer);
view.setUint16(0, 500);
console.log(view.getUint16(0)); // 500
Смотри, что важно: DataView работает в байтовых смещениях. В протоколах ты часто заранее фиксируешь layout: что лежит в байте 0, что в байте 1, где начинается Uint16 и т.д.
// header: version (1 byte), type (1 byte), length (Uint16)
const buffer = new ArrayBuffer(4);
const view = new DataView(buffer);
view.setUint8(0, 2); // version
view.setUint8(1, 9); // type
view.setUint16(2, 1024); // length
const version = view.getUint8(0);
const type = view.getUint8(1);
const length = view.getUint16(2);
console.log(version, type, length);
Смотри, что важно: у setUint16/getUint16 есть параметр littleEndian. Если перепутать порядок байтов при записи/чтении, числа будут "ломаться".
const buffer = new ArrayBuffer(2);
const view = new DataView(buffer);
view.setUint16(0, 500, true); // little-endian
console.log(view.getUint16(0, true)); // 500
console.log(view.getUint16(0, false)); // 62465 (прочитали в другом порядке)
Это полезно при работе с бинарными протоколами, где структура данных строго определена по байтам.
Реальные микро-сценарии
- Работа с
fetchи бинарным ответом (arrayBuffer()). - Обработка аудио-данных в браузере.
- Парсинг бинарного формата файла (например, заголовки изображений).
async function loadBinary(url) {
const response = await fetch(url);
const buffer = await response.arrayBuffer();
return new Uint8Array(buffer);
}
Дополнительный пример: выделение части буфера через subarray.
const bytes = new Uint8Array([10, 20, 30, 40, 50]);
const header = bytes.subarray(0, 2);
const payload = bytes.subarray(2);
console.log(header); // Uint8Array [10, 20]
console.log(payload); // Uint8Array [30, 40, 50]
Смотри, что важно: subarray(...) не копирует данные, а создает "окно" в том же буфере. Если изменить header[0], изменится и bytes[0]. Если нужен независимый кусок, используй slice(...) у typed array.
const bytes = new Uint8Array([10, 20, 30]);
const headView = bytes.subarray(0, 2);
const headCopy = bytes.slice(0, 2);
headView[0] = 99;
console.log(bytes[0]); // 99 (общая память)
console.log(headCopy[0]); // 10 (копия)
Дополнительный пример: восстановление текста из ASCII-байтов.
const codes = new Uint8Array([72, 73, 33]);
let text = '';
for (const code of codes) text += String.fromCharCode(code);
console.log(text); // HI!
Частые ошибки новичков
- Путать размер в байтах и количество элементов.
- Ожидать, что
ArrayBufferработает как обычный массив. - Игнорировать диапазоны типов (
Uint8не хранит -1 как вnumber). - Смешивать разные представления без понимания формата.
Анти-провал: сначала зафиксируй, какой именно формат данных ты читаешь (тип числа, длина, смещение), и только потом выбирай TypedArray.
Что будет, если изменить входные данные
Если в Uint8Array записать значение 300, оно будет приведено по модулю диапазона (получится 44). Это edge case, который часто удивляет новичков. Значит, перед записью стоит проверять диапазон, если важна точность.
Проверь себя: какой тип выбрать для хранения значения -20 - Uint8Array или Int16Array?
Краткий итог
ArrayBufferхранит сырые байты, аTypedArrayдает удобный доступ к ним.- Типизированные массивы важны для бинарных данных, медиа и низкоуровневых API.
- Разные представления над одним буфером видят одни и те же байты.
DataViewнужен для точного чтения/записи по смещению.- При работе с бинарными структурами критичны диапазоны типов и формат данных.