Ты интегрируешь устаревший корпоративный API, который отдаёт данные в кодировке Windows-1251 (не UTF-8). Fetch возвращает ArrayBuffer, а не строку. Без TextDecoder прочитать кириллицу невозможно — получишь кракозябры. В другом проекте нужно отправить JSON через WebSocket в бинарном режиме — TextEncoder превращает строку в байты.
Сеть и файлы работают с байтами. Строки в JavaScript — Unicode. TextEncoder/TextDecoder — мост между ними. Это единственный стандартный способ конвертировать строки в бинарные данные и обратно с поддержкой разных кодировок.
response.arrayBuffer() — когда сервер не UTF-8, нужен TextDecoderTextEncoder всегда кодирует в UTF-8:
const encoder = new TextEncoder()
const bytes = encoder.encode('Привет!')
console.log(bytes instanceof Uint8Array) // true
console.log(bytes.byteLength) // 13 (кириллица = 2 байта/символ)
// encodeInto() — пишет в существующий буфер (без аллокации)
const buf = new Uint8Array(100)
const { read, written } = encoder.encodeInto('Hello', buf)
console.log(written) // 5UTF-8 — переменная ширина. ASCII (U+0000–U+007F) — 1 байт. Кириллица (U+0400–U+04FF) — 2 байта. Китайские символы — 3 байта. Эмодзи — 4 байта:
const enc = new TextEncoder()
const sizes = [
['A', 1], // ASCII
['Я', 2], // Кириллица
['€', 3], // Европейский символ
['😀', 4], // Эмодзи
]
for (const [char] of sizes) {
console.log(char, '->', enc.encode(char).byteLength, 'байт')
}Поддерживает множество кодировок:
const utf8 = new TextDecoder() // utf-8 по умолчанию
const win = new TextDecoder('windows-1251') // кириллица Windows
const latin = new TextDecoder('iso-8859-1') // Western European
// Декодируем байты
const bytes = Uint8Array.from([72, 101, 108, 108, 111])
console.log(utf8.decode(bytes)) // 'Hello'При получении данных чанками (streaming) многобайтовый символ может прийти разорванным. Опция stream: true сохраняет состояние между вызовами:
const decoder = new TextDecoder('utf-8')
// Буква 'П' в UTF-8 = [0xD0, 0x9F]
// Чанк 1 содержит только первый байт 'П'
const chunk1 = new Uint8Array([0xD0])
const chunk2 = new Uint8Array([0x9F, 0x20, 0x41]) // '...' + ' A'
const part1 = decoder.decode(chunk1, { stream: true }) // '' — ждёт следующий байт
const part2 = decoder.decode(chunk2) // 'П A'
console.log(part1 + part2) // 'П A'Ошибка 1: Не учтено, что .length строки !== байты
const str = 'Привет'
console.log(str.length) // 6 символов
const bytes = new TextEncoder().encode(str)
console.log(bytes.byteLength) // 12 байт (кириллица = 2 байта!)
// В HTTP Content-Length нужно указывать БАЙТЫ, не символыОшибка 2: Отсутствие stream:true при потоковом чтении
// НЕВЕРНО — разрывает многобайтовые символы
const decoder = new TextDecoder()
chunks.forEach(chunk => {
result += decoder.decode(chunk) // символ может расколоться!
})
// ВЕРНО
const decoder = new TextDecoder()
chunks.forEach((chunk, i) => {
const isLast = i === chunks.length - 1
result += decoder.decode(chunk, { stream: !isLast })
})Ошибка 3: Неправильная кодировка для legacy API
// Если сервер отдаёт windows-1251, а ты декодируешь как utf-8:
const decoder = new TextDecoder('utf-8') // Кракозябры!
// Нужно явно указать кодировку:
const decoder2 = new TextDecoder('windows-1251') // Правильноresponse.arrayBuffer() + TextDecoder для legacy API с windows-1251crypto.subtle.importKeyWebSocket бинарный протокол: кодирование JSON сообщений и декодирование ответов с поддержкой кодировок
// ===== Симуляция бинарного WebSocket протокола =====
// Формат сообщения: [type: u8][bodyLen: u32 LE][body: UTF-8 bytes]
const MSG_TYPES = { JSON: 0x01, TEXT: 0x02, BINARY: 0x03 }
const encoder = new TextEncoder()
const decoder = new TextDecoder('utf-8')
function encodeMessage(type, data) {
// Сериализуем данные в UTF-8 байты
const bodyStr = typeof data === 'string' ? data : JSON.stringify(data)
const bodyBytes = encoder.encode(bodyStr)
// Заголовок: 1 байт тип + 4 байта длина = 5 байт
const header = new ArrayBuffer(5)
const view = new DataView(header)
view.setUint8(0, type)
view.setUint32(1, bodyBytes.byteLength, true)
// Собираем итоговый буфер
const packet = new Uint8Array(5 + bodyBytes.byteLength)
packet.set(new Uint8Array(header), 0)
packet.set(bodyBytes, 5)
return packet
}
function decodeMessage(packet) {
const view = new DataView(packet.buffer, packet.byteOffset, packet.byteLength)
const type = view.getUint8(0)
const bodyLen = view.getUint32(1, true)
const body = packet.slice(5, 5 + bodyLen)
const bodyStr = decoder.decode(body)
return {
type,
bodyLen,
data: type === MSG_TYPES.JSON ? JSON.parse(bodyStr) : bodyStr,
}
}
// Тест JSON-сообщения
console.log('=== Бинарный WebSocket протокол ===')
const userData = { id: 42, name: 'Иван', action: 'login' }
const packet = encodeMessage(MSG_TYPES.JSON, userData)
console.log('Размер пакета:', packet.byteLength, 'байт')
console.log('Заголовок (hex):', Array.from(packet.slice(0, 5))
.map(b => b.toString(16).padStart(2, '0')).join(' '))
const decoded = decodeMessage(packet)
console.log('Тип:', decoded.type.toString(16)) // 1
console.log('bodyLen:', decoded.bodyLen)
console.log('data.name:', decoded.data.name) // Иван
console.log('data.action:', decoded.data.action) // login
// ===== UTF-8 размеры символов =====
console.log('\n=== Размер символов в UTF-8 ===')
const chars = ['A', 'Я', '€', '😀', '中', '한']
for (const ch of chars) {
const bytes = encoder.encode(ch)
const hex = Array.from(bytes).map(b => '0x' + b.toString(16)).join(', ')
console.log(`'${ch}' → ${bytes.byteLength} байт [${hex}]`)
}
// ===== Измерение байтового размера строк =====
console.log('\n=== Размер строк в байтах ===')
function getByteSize(str) {
return encoder.encode(str).byteLength
}
const strings = [
'Hello',
'Привет',
'你好',
'Hello, Мир! 🌍',
]
for (const s of strings) {
const chars = s.length
const bytes = getByteSize(s)
console.log(`"${s}": ${chars} символов, ${bytes} байт, ${(bytes/chars).toFixed(1)} байт/символ`)
}
// ===== Потоковое декодирование =====
console.log('\n=== stream: true — потоковое декодирование ===')
// Симулируем получение текста чанками
const fullText = 'Привет, мир! Hello World!'
const allBytes = encoder.encode(fullText)
// Разбиваем на чанки по 7 байт (режем посередине кириллических символов)
const chunkSize = 7
const chunks = []
for (let i = 0; i < allBytes.length; i += chunkSize) {
chunks.push(allBytes.slice(i, i + chunkSize))
}
console.log('Исходный текст:', fullText)
console.log('Чанков:', chunks.length, '(по ~7 байт)')
// Без stream: true — ошибки декодирования
const badDecoder = new TextDecoder()
let badResult = ''
for (const chunk of chunks) {
badResult += badDecoder.decode(chunk) // символы могут испортиться
}
console.log('Без stream:true:', badResult.length === fullText.length ? 'OK' : 'Есть ошибки!')
// Со stream: true — корректно
const goodDecoder = new TextDecoder()
let goodResult = ''
chunks.forEach((chunk, i) => {
goodResult += goodDecoder.decode(chunk, { stream: i < chunks.length - 1 })
})
console.log('С stream:true:', goodResult)
console.log('Совпадает?', goodResult === fullText) // trueТы интегрируешь устаревший корпоративный API, который отдаёт данные в кодировке Windows-1251 (не UTF-8). Fetch возвращает ArrayBuffer, а не строку. Без TextDecoder прочитать кириллицу невозможно — получишь кракозябры. В другом проекте нужно отправить JSON через WebSocket в бинарном режиме — TextEncoder превращает строку в байты.
Сеть и файлы работают с байтами. Строки в JavaScript — Unicode. TextEncoder/TextDecoder — мост между ними. Это единственный стандартный способ конвертировать строки в бинарные данные и обратно с поддержкой разных кодировок.
response.arrayBuffer() — когда сервер не UTF-8, нужен TextDecoderTextEncoder всегда кодирует в UTF-8:
const encoder = new TextEncoder()
const bytes = encoder.encode('Привет!')
console.log(bytes instanceof Uint8Array) // true
console.log(bytes.byteLength) // 13 (кириллица = 2 байта/символ)
// encodeInto() — пишет в существующий буфер (без аллокации)
const buf = new Uint8Array(100)
const { read, written } = encoder.encodeInto('Hello', buf)
console.log(written) // 5UTF-8 — переменная ширина. ASCII (U+0000–U+007F) — 1 байт. Кириллица (U+0400–U+04FF) — 2 байта. Китайские символы — 3 байта. Эмодзи — 4 байта:
const enc = new TextEncoder()
const sizes = [
['A', 1], // ASCII
['Я', 2], // Кириллица
['€', 3], // Европейский символ
['😀', 4], // Эмодзи
]
for (const [char] of sizes) {
console.log(char, '->', enc.encode(char).byteLength, 'байт')
}Поддерживает множество кодировок:
const utf8 = new TextDecoder() // utf-8 по умолчанию
const win = new TextDecoder('windows-1251') // кириллица Windows
const latin = new TextDecoder('iso-8859-1') // Western European
// Декодируем байты
const bytes = Uint8Array.from([72, 101, 108, 108, 111])
console.log(utf8.decode(bytes)) // 'Hello'При получении данных чанками (streaming) многобайтовый символ может прийти разорванным. Опция stream: true сохраняет состояние между вызовами:
const decoder = new TextDecoder('utf-8')
// Буква 'П' в UTF-8 = [0xD0, 0x9F]
// Чанк 1 содержит только первый байт 'П'
const chunk1 = new Uint8Array([0xD0])
const chunk2 = new Uint8Array([0x9F, 0x20, 0x41]) // '...' + ' A'
const part1 = decoder.decode(chunk1, { stream: true }) // '' — ждёт следующий байт
const part2 = decoder.decode(chunk2) // 'П A'
console.log(part1 + part2) // 'П A'Ошибка 1: Не учтено, что .length строки !== байты
const str = 'Привет'
console.log(str.length) // 6 символов
const bytes = new TextEncoder().encode(str)
console.log(bytes.byteLength) // 12 байт (кириллица = 2 байта!)
// В HTTP Content-Length нужно указывать БАЙТЫ, не символыОшибка 2: Отсутствие stream:true при потоковом чтении
// НЕВЕРНО — разрывает многобайтовые символы
const decoder = new TextDecoder()
chunks.forEach(chunk => {
result += decoder.decode(chunk) // символ может расколоться!
})
// ВЕРНО
const decoder = new TextDecoder()
chunks.forEach((chunk, i) => {
const isLast = i === chunks.length - 1
result += decoder.decode(chunk, { stream: !isLast })
})Ошибка 3: Неправильная кодировка для legacy API
// Если сервер отдаёт windows-1251, а ты декодируешь как utf-8:
const decoder = new TextDecoder('utf-8') // Кракозябры!
// Нужно явно указать кодировку:
const decoder2 = new TextDecoder('windows-1251') // Правильноresponse.arrayBuffer() + TextDecoder для legacy API с windows-1251crypto.subtle.importKeyWebSocket бинарный протокол: кодирование JSON сообщений и декодирование ответов с поддержкой кодировок
// ===== Симуляция бинарного WebSocket протокола =====
// Формат сообщения: [type: u8][bodyLen: u32 LE][body: UTF-8 bytes]
const MSG_TYPES = { JSON: 0x01, TEXT: 0x02, BINARY: 0x03 }
const encoder = new TextEncoder()
const decoder = new TextDecoder('utf-8')
function encodeMessage(type, data) {
// Сериализуем данные в UTF-8 байты
const bodyStr = typeof data === 'string' ? data : JSON.stringify(data)
const bodyBytes = encoder.encode(bodyStr)
// Заголовок: 1 байт тип + 4 байта длина = 5 байт
const header = new ArrayBuffer(5)
const view = new DataView(header)
view.setUint8(0, type)
view.setUint32(1, bodyBytes.byteLength, true)
// Собираем итоговый буфер
const packet = new Uint8Array(5 + bodyBytes.byteLength)
packet.set(new Uint8Array(header), 0)
packet.set(bodyBytes, 5)
return packet
}
function decodeMessage(packet) {
const view = new DataView(packet.buffer, packet.byteOffset, packet.byteLength)
const type = view.getUint8(0)
const bodyLen = view.getUint32(1, true)
const body = packet.slice(5, 5 + bodyLen)
const bodyStr = decoder.decode(body)
return {
type,
bodyLen,
data: type === MSG_TYPES.JSON ? JSON.parse(bodyStr) : bodyStr,
}
}
// Тест JSON-сообщения
console.log('=== Бинарный WebSocket протокол ===')
const userData = { id: 42, name: 'Иван', action: 'login' }
const packet = encodeMessage(MSG_TYPES.JSON, userData)
console.log('Размер пакета:', packet.byteLength, 'байт')
console.log('Заголовок (hex):', Array.from(packet.slice(0, 5))
.map(b => b.toString(16).padStart(2, '0')).join(' '))
const decoded = decodeMessage(packet)
console.log('Тип:', decoded.type.toString(16)) // 1
console.log('bodyLen:', decoded.bodyLen)
console.log('data.name:', decoded.data.name) // Иван
console.log('data.action:', decoded.data.action) // login
// ===== UTF-8 размеры символов =====
console.log('\n=== Размер символов в UTF-8 ===')
const chars = ['A', 'Я', '€', '😀', '中', '한']
for (const ch of chars) {
const bytes = encoder.encode(ch)
const hex = Array.from(bytes).map(b => '0x' + b.toString(16)).join(', ')
console.log(`'${ch}' → ${bytes.byteLength} байт [${hex}]`)
}
// ===== Измерение байтового размера строк =====
console.log('\n=== Размер строк в байтах ===')
function getByteSize(str) {
return encoder.encode(str).byteLength
}
const strings = [
'Hello',
'Привет',
'你好',
'Hello, Мир! 🌍',
]
for (const s of strings) {
const chars = s.length
const bytes = getByteSize(s)
console.log(`"${s}": ${chars} символов, ${bytes} байт, ${(bytes/chars).toFixed(1)} байт/символ`)
}
// ===== Потоковое декодирование =====
console.log('\n=== stream: true — потоковое декодирование ===')
// Симулируем получение текста чанками
const fullText = 'Привет, мир! Hello World!'
const allBytes = encoder.encode(fullText)
// Разбиваем на чанки по 7 байт (режем посередине кириллических символов)
const chunkSize = 7
const chunks = []
for (let i = 0; i < allBytes.length; i += chunkSize) {
chunks.push(allBytes.slice(i, i + chunkSize))
}
console.log('Исходный текст:', fullText)
console.log('Чанков:', chunks.length, '(по ~7 байт)')
// Без stream: true — ошибки декодирования
const badDecoder = new TextDecoder()
let badResult = ''
for (const chunk of chunks) {
badResult += badDecoder.decode(chunk) // символы могут испортиться
}
console.log('Без stream:true:', badResult.length === fullText.length ? 'OK' : 'Есть ошибки!')
// Со stream: true — корректно
const goodDecoder = new TextDecoder()
let goodResult = ''
chunks.forEach((chunk, i) => {
goodResult += goodDecoder.decode(chunk, { stream: i < chunks.length - 1 })
})
console.log('С stream:true:', goodResult)
console.log('Совпадает?', goodResult === fullText) // trueТы получаешь данные от legacy API в формате CSV, закодированном в UTF-8 байтах (ArrayBuffer). Нужно декодировать данные и обработать их. Реализуй: - `encodeTo(data)` — принимает JS-объект, сериализует в JSON, возвращает Uint8Array - `decodeFrom(bytes)` — принимает Uint8Array, декодирует UTF-8, парсит JSON - `getByteSize(str)` — возвращает размер строки в байтах при кодировке UTF-8 - `parseCSVBytes(bytes)` — принимает Uint8Array с CSV-данными (первая строка — заголовки), возвращает массив объектов
encodeTo: JSON.stringify + new TextEncoder().encode(). decodeFrom: new TextDecoder().decode() + JSON.parse(). getByteSize: encoder.encode(str).byteLength. parseCSVBytes: decoder.decode(bytes) затем split и map