Представь, что ты разрабатываешь мессенджер и отправляешь голосовые сообщения. Сервер возвращает не текст, а поток байтов — сжатый аудиофайл. WebSocket доставляет эти байты как ArrayBuffer. Тебе нужно прочитать заголовок, понять кодек, размер, канал — всё это делается через TypedArrays и DataView. Без этих инструментов бинарный протокол недоступен.
JavaScript исторически работал только со строками и числами. Но сеть, файлы, WebGL, WebAssembly — всё это бинарные данные. ArrayBuffer даёт прямой доступ к байтам памяти.
response.arrayBuffer() возвращает ArrayBuffer с телом ответаArrayBuffer — сырой блок памяти фиксированного размера. Читать байты напрямую нельзя — нужно создать представление (view):
const buffer = new ArrayBuffer(16) // 16 байт, все нули
console.log(buffer.byteLength) // 16TypedArray — «линза», через которую интерпретируются байты:
| Тип | Байт/эл | Диапазон | Применение |
|---|---|---|---|
| Uint8Array | 1 | 0..255 | Сырые байты, PNG/JPEG |
| Int16Array | 2 | -32768..32767 | Аудио-сэмплы |
| Int32Array | 4 | ±2 млрд | Координаты, индексы |
| Float32Array | 4 | float | WebGL вершины |
| Float64Array | 8 | double | Точные вычисления |
const buffer = new ArrayBuffer(8)
const bytes = new Uint8Array(buffer) // 8 элементов по 1 байту
const ints = new Int32Array(buffer) // 2 элемента по 4 байта
// Оба смотрят на ОДИН И ТОТ ЖЕ буфер
bytes[0] = 255
bytes[1] = 0
console.log(ints[0]) // -256 (те же байты, другая интерпретация)Когда структура содержит поля разных типов (как заголовок бинарного файла), DataView позволяет читать произвольные типы по смещению:
const buf = new ArrayBuffer(10)
const view = new DataView(buf)
view.setUint8(0, 0xFF) // 1 байт по смещению 0
view.setUint16(1, 1024, true) // 2 байта, little-endian
view.setFloat32(3, 3.14, true) // 4 байта
console.log(view.getUint8(0)) // 255
console.log(view.getUint16(1, true)) // 1024
console.log(view.getFloat32(3, true).toFixed(2)) // 3.14const src = Uint8Array.from([1, 2, 3, 4, 5])
const copy = src.slice(1, 4) // КОПИЯ новых данных: [2, 3, 4]
const view = src.subarray(1, 4) // Тот же буфер, другие границы: [2, 3, 4]
view[0] = 99 // Изменяет src!
console.log(Array.from(src)) // [1, 99, 3, 4, 5]
console.log(Array.from(copy)) // [2, 3, 4] — не изменилсяОшибка 1: Попытка читать ArrayBuffer напрямую
// НЕВЕРНО
const buf = new ArrayBuffer(4)
console.log(buf[0]) // undefined — у ArrayBuffer нет индексов!
// ВЕРНО
const view = new Uint8Array(buf)
console.log(view[0]) // 0Ошибка 2: Несовпадение выравнивания
// НЕВЕРНО — Int32Array требует смещение кратное 4
const buf = new ArrayBuffer(8)
const bad = new Int32Array(buf, 1) // RangeError: byte offset is not aligned
// ВЕРНО
const good = new Int32Array(buf, 0) // смещение 0 или 4Ошибка 3: Путаница big-endian / little-endian
const buf = new ArrayBuffer(2)
const view = new DataView(buf)
view.setUint16(0, 0x0102, true) // little-endian: байты [02, 01]
view.setUint16(0, 0x0102, false) // big-endian: байты [01, 02]
// Сетевой порядок байтов — big-endian. x86 CPU — little-endian.Бинарный протокол: создание и парсинг заголовка сетевого пакета с DataView
// ===== Сетевой пакет: бинарный протокол =====
// Формат: [version: u8][type: u8][payloadLen: u32 LE][checksum: u16 LE]
// Итого: 1 + 1 + 4 + 2 = 8 байт заголовка
const PACKET_TYPES = { DATA: 0x01, ACK: 0x02, PING: 0x03, ERROR: 0xFF }
function createPacketHeader(type, payloadLength) {
const buf = new ArrayBuffer(8)
const view = new DataView(buf)
view.setUint8(0, 1) // version = 1
view.setUint8(1, type) // тип пакета
view.setUint32(2, payloadLength, true) // длина данных, LE
// checksum — XOR всех предыдущих байт (упрощённый)
const bytes = new Uint8Array(buf)
let checksum = 0
for (let i = 0; i < 6; i++) checksum ^= bytes[i]
view.setUint16(6, checksum, true)
return buf
}
function parsePacketHeader(buf) {
const view = new DataView(buf)
const bytes = new Uint8Array(buf)
const version = view.getUint8(0)
const type = view.getUint8(1)
const payloadLen = view.getUint32(2, true)
const checksum = view.getUint16(6, true)
// Верифицируем checksum
let expected = 0
for (let i = 0; i < 6; i++) expected ^= bytes[i]
const valid = checksum === expected
return { version, type, payloadLen, checksum, valid }
}
console.log('=== Создание пакетов ===')
const pingBuf = createPacketHeader(PACKET_TYPES.PING, 0)
const dataBuf = createPacketHeader(PACKET_TYPES.DATA, 1024)
const pingRaw = Array.from(new Uint8Array(pingBuf))
.map(b => b.toString(16).padStart(2, '0')).join(' ')
console.log('PING заголовок (hex):', pingRaw)
// 01 03 00 00 00 00 02 00
const pingInfo = parsePacketHeader(pingBuf)
console.log('PING разобран:', pingInfo)
// { version: 1, type: 3, payloadLen: 0, checksum: 2, valid: true }
const dataInfo = parsePacketHeader(dataBuf)
console.log('DATA разобран:', dataInfo)
// { version: 1, type: 1, payloadLen: 1024, ..., valid: true }
// ===== TypedArray — работа с аудио PCM =====
console.log('\n=== Симуляция PCM аудио-буфера ===')
// Аудио-сэмплы: 16-bit signed, 44100 Hz, 0.1 секунды = 4410 сэмплов
const SAMPLE_RATE = 44100
const DURATION_MS = 100
const numSamples = Math.floor(SAMPLE_RATE * DURATION_MS / 1000)
const audioBuffer = new Int16Array(numSamples)
// Генерируем синусоиду 440 Hz (нота A4)
const FREQ = 440
const AMPLITUDE = 32767 * 0.5 // 50% громкость
for (let i = 0; i < numSamples; i++) {
const t = i / SAMPLE_RATE
audioBuffer[i] = Math.round(AMPLITUDE * Math.sin(2 * Math.PI * FREQ * t))
}
console.log('Сэмплов:', audioBuffer.length)
console.log('Байт буфера:', audioBuffer.byteLength)
console.log('Первые 5 сэмплов:', Array.from(audioBuffer.slice(0, 5)))
// Значения ~ около 0 нарастают (синусоида начинается с 0)
// Анализ: находим пик амплитуды
let maxAmp = 0
for (let i = 0; i < audioBuffer.length; i++) {
if (Math.abs(audioBuffer[i]) > maxAmp) maxAmp = Math.abs(audioBuffer[i])
}
console.log('Пиковая амплитуда:', maxAmp)
console.log('Пиковая амплитуда (%):', Math.round(maxAmp / 32767 * 100) + '%')
// ===== slice vs subarray =====
console.log('\n=== slice (копия) vs subarray (view) ===')
const src = Uint8Array.from([10, 20, 30, 40, 50])
const copy = src.slice(1, 4) // [20, 30, 40] — независимая копия
const view = src.subarray(1, 4) // [20, 30, 40] — тот же буфер
view[0] = 99 // меняем через view
console.log('src после view[0]=99:', Array.from(src)) // [10, 99, 30, 40, 50]
console.log('copy — не изменился:', Array.from(copy)) // [20, 30, 40]Представь, что ты разрабатываешь мессенджер и отправляешь голосовые сообщения. Сервер возвращает не текст, а поток байтов — сжатый аудиофайл. WebSocket доставляет эти байты как ArrayBuffer. Тебе нужно прочитать заголовок, понять кодек, размер, канал — всё это делается через TypedArrays и DataView. Без этих инструментов бинарный протокол недоступен.
JavaScript исторически работал только со строками и числами. Но сеть, файлы, WebGL, WebAssembly — всё это бинарные данные. ArrayBuffer даёт прямой доступ к байтам памяти.
response.arrayBuffer() возвращает ArrayBuffer с телом ответаArrayBuffer — сырой блок памяти фиксированного размера. Читать байты напрямую нельзя — нужно создать представление (view):
const buffer = new ArrayBuffer(16) // 16 байт, все нули
console.log(buffer.byteLength) // 16TypedArray — «линза», через которую интерпретируются байты:
| Тип | Байт/эл | Диапазон | Применение |
|---|---|---|---|
| Uint8Array | 1 | 0..255 | Сырые байты, PNG/JPEG |
| Int16Array | 2 | -32768..32767 | Аудио-сэмплы |
| Int32Array | 4 | ±2 млрд | Координаты, индексы |
| Float32Array | 4 | float | WebGL вершины |
| Float64Array | 8 | double | Точные вычисления |
const buffer = new ArrayBuffer(8)
const bytes = new Uint8Array(buffer) // 8 элементов по 1 байту
const ints = new Int32Array(buffer) // 2 элемента по 4 байта
// Оба смотрят на ОДИН И ТОТ ЖЕ буфер
bytes[0] = 255
bytes[1] = 0
console.log(ints[0]) // -256 (те же байты, другая интерпретация)Когда структура содержит поля разных типов (как заголовок бинарного файла), DataView позволяет читать произвольные типы по смещению:
const buf = new ArrayBuffer(10)
const view = new DataView(buf)
view.setUint8(0, 0xFF) // 1 байт по смещению 0
view.setUint16(1, 1024, true) // 2 байта, little-endian
view.setFloat32(3, 3.14, true) // 4 байта
console.log(view.getUint8(0)) // 255
console.log(view.getUint16(1, true)) // 1024
console.log(view.getFloat32(3, true).toFixed(2)) // 3.14const src = Uint8Array.from([1, 2, 3, 4, 5])
const copy = src.slice(1, 4) // КОПИЯ новых данных: [2, 3, 4]
const view = src.subarray(1, 4) // Тот же буфер, другие границы: [2, 3, 4]
view[0] = 99 // Изменяет src!
console.log(Array.from(src)) // [1, 99, 3, 4, 5]
console.log(Array.from(copy)) // [2, 3, 4] — не изменилсяОшибка 1: Попытка читать ArrayBuffer напрямую
// НЕВЕРНО
const buf = new ArrayBuffer(4)
console.log(buf[0]) // undefined — у ArrayBuffer нет индексов!
// ВЕРНО
const view = new Uint8Array(buf)
console.log(view[0]) // 0Ошибка 2: Несовпадение выравнивания
// НЕВЕРНО — Int32Array требует смещение кратное 4
const buf = new ArrayBuffer(8)
const bad = new Int32Array(buf, 1) // RangeError: byte offset is not aligned
// ВЕРНО
const good = new Int32Array(buf, 0) // смещение 0 или 4Ошибка 3: Путаница big-endian / little-endian
const buf = new ArrayBuffer(2)
const view = new DataView(buf)
view.setUint16(0, 0x0102, true) // little-endian: байты [02, 01]
view.setUint16(0, 0x0102, false) // big-endian: байты [01, 02]
// Сетевой порядок байтов — big-endian. x86 CPU — little-endian.Бинарный протокол: создание и парсинг заголовка сетевого пакета с DataView
// ===== Сетевой пакет: бинарный протокол =====
// Формат: [version: u8][type: u8][payloadLen: u32 LE][checksum: u16 LE]
// Итого: 1 + 1 + 4 + 2 = 8 байт заголовка
const PACKET_TYPES = { DATA: 0x01, ACK: 0x02, PING: 0x03, ERROR: 0xFF }
function createPacketHeader(type, payloadLength) {
const buf = new ArrayBuffer(8)
const view = new DataView(buf)
view.setUint8(0, 1) // version = 1
view.setUint8(1, type) // тип пакета
view.setUint32(2, payloadLength, true) // длина данных, LE
// checksum — XOR всех предыдущих байт (упрощённый)
const bytes = new Uint8Array(buf)
let checksum = 0
for (let i = 0; i < 6; i++) checksum ^= bytes[i]
view.setUint16(6, checksum, true)
return buf
}
function parsePacketHeader(buf) {
const view = new DataView(buf)
const bytes = new Uint8Array(buf)
const version = view.getUint8(0)
const type = view.getUint8(1)
const payloadLen = view.getUint32(2, true)
const checksum = view.getUint16(6, true)
// Верифицируем checksum
let expected = 0
for (let i = 0; i < 6; i++) expected ^= bytes[i]
const valid = checksum === expected
return { version, type, payloadLen, checksum, valid }
}
console.log('=== Создание пакетов ===')
const pingBuf = createPacketHeader(PACKET_TYPES.PING, 0)
const dataBuf = createPacketHeader(PACKET_TYPES.DATA, 1024)
const pingRaw = Array.from(new Uint8Array(pingBuf))
.map(b => b.toString(16).padStart(2, '0')).join(' ')
console.log('PING заголовок (hex):', pingRaw)
// 01 03 00 00 00 00 02 00
const pingInfo = parsePacketHeader(pingBuf)
console.log('PING разобран:', pingInfo)
// { version: 1, type: 3, payloadLen: 0, checksum: 2, valid: true }
const dataInfo = parsePacketHeader(dataBuf)
console.log('DATA разобран:', dataInfo)
// { version: 1, type: 1, payloadLen: 1024, ..., valid: true }
// ===== TypedArray — работа с аудио PCM =====
console.log('\n=== Симуляция PCM аудио-буфера ===')
// Аудио-сэмплы: 16-bit signed, 44100 Hz, 0.1 секунды = 4410 сэмплов
const SAMPLE_RATE = 44100
const DURATION_MS = 100
const numSamples = Math.floor(SAMPLE_RATE * DURATION_MS / 1000)
const audioBuffer = new Int16Array(numSamples)
// Генерируем синусоиду 440 Hz (нота A4)
const FREQ = 440
const AMPLITUDE = 32767 * 0.5 // 50% громкость
for (let i = 0; i < numSamples; i++) {
const t = i / SAMPLE_RATE
audioBuffer[i] = Math.round(AMPLITUDE * Math.sin(2 * Math.PI * FREQ * t))
}
console.log('Сэмплов:', audioBuffer.length)
console.log('Байт буфера:', audioBuffer.byteLength)
console.log('Первые 5 сэмплов:', Array.from(audioBuffer.slice(0, 5)))
// Значения ~ около 0 нарастают (синусоида начинается с 0)
// Анализ: находим пик амплитуды
let maxAmp = 0
for (let i = 0; i < audioBuffer.length; i++) {
if (Math.abs(audioBuffer[i]) > maxAmp) maxAmp = Math.abs(audioBuffer[i])
}
console.log('Пиковая амплитуда:', maxAmp)
console.log('Пиковая амплитуда (%):', Math.round(maxAmp / 32767 * 100) + '%')
// ===== slice vs subarray =====
console.log('\n=== slice (копия) vs subarray (view) ===')
const src = Uint8Array.from([10, 20, 30, 40, 50])
const copy = src.slice(1, 4) // [20, 30, 40] — независимая копия
const view = src.subarray(1, 4) // [20, 30, 40] — тот же буфер
view[0] = 99 // меняем через view
console.log('src после view[0]=99:', Array.from(src)) // [10, 99, 30, 40, 50]
console.log('copy — не изменился:', Array.from(copy)) // [20, 30, 40]Ты разрабатываешь бинарный протокол для передачи координат GPS между устройствами. Каждая точка хранится в 8 байтах: широта (Float32, смещение 0) и долгота (Float32, смещение 4), оба — little-endian. Реализуй: - `encodeGPS(lat, lng)` — упаковывает координаты в ArrayBuffer из 8 байт - `decodeGPS(buf)` — читает координаты обратно из буфера - `encodeBatch(points)` — упаковывает массив точек `[{lat, lng}]` в один ArrayBuffer (8 байт × N)
encodeGPS: new ArrayBuffer(8), setFloat32(0, lat, true), setFloat32(4, lng, true). decodeGPS: getFloat32(0, true), getFloat32(4, true). encodeBatch: new ArrayBuffer(8 * points.length), offset = i * 8