В дашборде аналитики пользователь жмёт «Экспорт в CSV» — и через секунду файл скачивается. Никакого сервера. Данные уже в браузере, Blob превращает их в файл. В другом сценарии: пользователь загружает аватар — ты создаёшь Blob URL для предпросмотра до отправки на сервер. Без Blob это невозможно.
Файловые операции в браузере требуют объекта, который ведёт себя как файл: имеет размер, MIME-тип, поддерживает чтение по частям. Blob (Binary Large Object) — именно такой объект. Он иммутабельный и высокоуровневый, в отличие от ArrayBuffer.
.arrayBuffer().text(), .arrayBuffer() возвращают Promise// Из строк
const textBlob = new Blob(['Привет!'], { type: 'text/plain; charset=utf-8' })
console.log(textBlob.size) // 13 байт (кириллица UTF-8)
console.log(textBlob.type) // 'text/plain; charset=utf-8'
// Из нескольких частей (конкатенация)
const html = new Blob(
['<!DOCTYPE html>', '<html>', '<body>Привет</body>', '</html>'],
{ type: 'text/html' }
)
// Из ArrayBuffer / TypedArray
const bytes = new Blob([new Uint8Array([137, 80, 78, 71])]) // PNG magic bytesconst blob = new Blob(['Hello, Blob!'])
// Современный API — возвращают Promise
const text = await blob.text() // 'Hello, Blob!'
const buf = await blob.arrayBuffer() // ArrayBuffer
// blob.stream() // ReadableStream (потоковое чтение)
// Размер и тип
console.log(blob.size) // 11 байт
console.log(blob.type) // '' (не указан)Позволяет читать большие файлы кусками без загрузки в память целиком:
const big = new Blob(['ABCDEFGHIJ'])
const part = big.slice(2, 6) // новый Blob: байты 2..5
const text = await part.text()
console.log(text) // 'CDEF'
// Читаем только заголовок большого файла (первые 512 байт)
const header = file.slice(0, 512)
const magic = await header.arrayBuffer()Важнейший паттерн: генерация файла прямо в браузере и скачивание без сервера:
function downloadFile(content, filename, mimeType) {
const blob = new Blob([content], { type: mimeType })
const url = URL.createObjectURL(blob) // 'blob:https://...'
const link = document.createElement('a')
link.href = url
link.download = filename
link.click()
URL.revokeObjectURL(url) // Освобождаем память!
}В sandbox-среде без DOM создаём Blob и URL, просто не переходим по ним.
Ошибка 1: Забыть revokeObjectURL
// НЕВЕРНО — утечка памяти: blob-URL живёт до закрытия вкладки
const url = URL.createObjectURL(blob)
link.href = url
link.click()
// Забыли URL.revokeObjectURL(url)!
// ВЕРНО — освобождаем сразу после использования
link.addEventListener('click', () => {
setTimeout(() => URL.revokeObjectURL(url), 1000)
})Ошибка 2: Неправильный MIME-тип
// CSV без кодировки — Excel откроет с кракозябрами
const bad = new Blob([csv], { type: 'text/csv' })
// ВЕРНО — указываем кодировку
const good = new Blob(['' + csv], { type: 'text/csv; charset=utf-8' })
// '' — BOM-маркер, помогает Excel понять UTF-8Ошибка 3: Передача Blob туда, где нужен ArrayBuffer
// НЕВЕРНО — Blob не является ArrayBuffer
const blob = new Blob([data])
new DataView(blob) // TypeError!
// ВЕРНО — конвертировать явно
const buf = await blob.arrayBuffer()
const view = new DataView(buf)<img src> до загрузки на серверformData.append('file', blob, 'data.json')Генератор отчётов: экспорт CSV и JSON, предпросмотр через Blob URL, загрузка по частям
// ===== Генератор отчётов с Blob =====
// Функция генерации CSV с BOM для корректного отображения в Excel
function generateCSV(headers, rows) {
const BOM = '' // UTF-8 BOM для Excel
const headerRow = headers.join(',')
const dataRows = rows.map(row =>
headers.map(h => {
const val = String(row[h] ?? '')
// Экранируем запятые и кавычки
return val.includes(',') || val.includes('"')
? '"' + val.replace(/"/g, '""') + '"'
: val
}).join(',')
)
return BOM + [headerRow, ...dataRows].join('\n')
}
// ===== Тестовые данные =====
const sales = [
{ product: 'Ноутбук', qty: 15, price: 85000, total: 1275000 },
{ product: 'Мышь', qty: 80, price: 1200, total: 96000 },
{ product: 'Клавиатура', qty: 50, price: 3500, total: 175000 },
{ product: 'Монитор', qty: 20, price: 25000, total: 500000 },
]
// CSV экспорт
console.log('=== CSV экспорт ===')
const csvContent = generateCSV(['product', 'qty', 'price', 'total'], sales)
const csvBlob = new Blob([csvContent], { type: 'text/csv; charset=utf-8' })
console.log('CSV размер:', csvBlob.size, 'байт')
console.log('CSV тип:', csvBlob.type)
const csvUrl = URL.createObjectURL(csvBlob)
console.log('URL для скачивания:', csvUrl.substring(0, 20) + '...')
URL.revokeObjectURL(csvUrl)
console.log('URL освобождён')
// JSON экспорт с метаданными
console.log('\n=== JSON экспорт ===')
async function exportJSON(data, meta = {}) {
const payload = {
exportedAt: new Date().toISOString(),
version: '1.0',
...meta,
data,
}
const json = JSON.stringify(payload, null, 2)
const blob = new Blob([json], { type: 'application/json' })
// Читаем обратно для проверки
const text = await blob.text()
const parsed = JSON.parse(text)
console.log('JSON blob размер:', blob.size, 'байт')
console.log('Записей в данных:', parsed.data.length)
console.log('Первый товар:', parsed.data[0].product)
console.log('Экспортирован:', parsed.exportedAt.substring(0, 10))
return blob
}
exportJSON(sales, { source: 'sales-dashboard' }).then(blob => {
console.log('Итоговый размер JSON:', blob.size, 'байт')
})
// ===== slice: чтение по частям =====
console.log('\n=== Blob.slice — чтение по частям ===')
async function readInChunks(blob, chunkSize) {
const chunks = []
let offset = 0
while (offset < blob.size) {
const chunk = blob.slice(offset, offset + chunkSize)
const chunkText = await chunk.text()
chunks.push(chunkText)
offset += chunkSize
}
return chunks.join('')
}
async function sliceDemo() {
const text = 'Строка 1\nСтрока 2\nСтрока 3\nСтрока 4\nСтрока 5'
const blob = new Blob([text])
console.log('Размер blob:', blob.size, 'байт')
// Читаем заголовок (первые 10 байт)
const header = await blob.slice(0, 10).text()
console.log('Первые 10 байт:', JSON.stringify(header))
// Читаем по 15-байтовым чанкам
const assembled = await readInChunks(blob, 15)
console.log('Собрано чанков:', Math.ceil(blob.size / 15))
console.log('Текст восстановлен:', assembled === text)
}
sliceDemo()
// ===== Blob → ArrayBuffer → Blob =====
console.log('\n=== Конвертация Blob ↔ ArrayBuffer ===')
async function convertDemo() {
// Создаём Blob с произвольными бинарными данными
const original = new Uint8Array([0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A])
// Это PNG magic bytes!
const blob = new Blob([original], { type: 'image/png' })
// Blob → ArrayBuffer
const buf = await blob.arrayBuffer()
const bytes = new Uint8Array(buf)
const hex = Array.from(bytes).map(b => b.toString(16).padStart(2, '0')).join(' ')
console.log('PNG magic bytes:', hex)
// 89 50 4e 47 0d 0a 1a 0a
// Проверяем: это PNG?
const isPNG = bytes[0] === 0x89 && bytes[1] === 0x50 &&
bytes[2] === 0x4E && bytes[3] === 0x47
console.log('Это PNG?', isPNG) // true
// ArrayBuffer → новый Blob
const restored = new Blob([buf], { type: blob.type })
console.log('Восстановленный тип:', restored.type) // image/png
console.log('Размер совпадает:', restored.size === blob.size) // true
}
convertDemo()В дашборде аналитики пользователь жмёт «Экспорт в CSV» — и через секунду файл скачивается. Никакого сервера. Данные уже в браузере, Blob превращает их в файл. В другом сценарии: пользователь загружает аватар — ты создаёшь Blob URL для предпросмотра до отправки на сервер. Без Blob это невозможно.
Файловые операции в браузере требуют объекта, который ведёт себя как файл: имеет размер, MIME-тип, поддерживает чтение по частям. Blob (Binary Large Object) — именно такой объект. Он иммутабельный и высокоуровневый, в отличие от ArrayBuffer.
.arrayBuffer().text(), .arrayBuffer() возвращают Promise// Из строк
const textBlob = new Blob(['Привет!'], { type: 'text/plain; charset=utf-8' })
console.log(textBlob.size) // 13 байт (кириллица UTF-8)
console.log(textBlob.type) // 'text/plain; charset=utf-8'
// Из нескольких частей (конкатенация)
const html = new Blob(
['<!DOCTYPE html>', '<html>', '<body>Привет</body>', '</html>'],
{ type: 'text/html' }
)
// Из ArrayBuffer / TypedArray
const bytes = new Blob([new Uint8Array([137, 80, 78, 71])]) // PNG magic bytesconst blob = new Blob(['Hello, Blob!'])
// Современный API — возвращают Promise
const text = await blob.text() // 'Hello, Blob!'
const buf = await blob.arrayBuffer() // ArrayBuffer
// blob.stream() // ReadableStream (потоковое чтение)
// Размер и тип
console.log(blob.size) // 11 байт
console.log(blob.type) // '' (не указан)Позволяет читать большие файлы кусками без загрузки в память целиком:
const big = new Blob(['ABCDEFGHIJ'])
const part = big.slice(2, 6) // новый Blob: байты 2..5
const text = await part.text()
console.log(text) // 'CDEF'
// Читаем только заголовок большого файла (первые 512 байт)
const header = file.slice(0, 512)
const magic = await header.arrayBuffer()Важнейший паттерн: генерация файла прямо в браузере и скачивание без сервера:
function downloadFile(content, filename, mimeType) {
const blob = new Blob([content], { type: mimeType })
const url = URL.createObjectURL(blob) // 'blob:https://...'
const link = document.createElement('a')
link.href = url
link.download = filename
link.click()
URL.revokeObjectURL(url) // Освобождаем память!
}В sandbox-среде без DOM создаём Blob и URL, просто не переходим по ним.
Ошибка 1: Забыть revokeObjectURL
// НЕВЕРНО — утечка памяти: blob-URL живёт до закрытия вкладки
const url = URL.createObjectURL(blob)
link.href = url
link.click()
// Забыли URL.revokeObjectURL(url)!
// ВЕРНО — освобождаем сразу после использования
link.addEventListener('click', () => {
setTimeout(() => URL.revokeObjectURL(url), 1000)
})Ошибка 2: Неправильный MIME-тип
// CSV без кодировки — Excel откроет с кракозябрами
const bad = new Blob([csv], { type: 'text/csv' })
// ВЕРНО — указываем кодировку
const good = new Blob(['' + csv], { type: 'text/csv; charset=utf-8' })
// '' — BOM-маркер, помогает Excel понять UTF-8Ошибка 3: Передача Blob туда, где нужен ArrayBuffer
// НЕВЕРНО — Blob не является ArrayBuffer
const blob = new Blob([data])
new DataView(blob) // TypeError!
// ВЕРНО — конвертировать явно
const buf = await blob.arrayBuffer()
const view = new DataView(buf)<img src> до загрузки на серверformData.append('file', blob, 'data.json')Генератор отчётов: экспорт CSV и JSON, предпросмотр через Blob URL, загрузка по частям
// ===== Генератор отчётов с Blob =====
// Функция генерации CSV с BOM для корректного отображения в Excel
function generateCSV(headers, rows) {
const BOM = '' // UTF-8 BOM для Excel
const headerRow = headers.join(',')
const dataRows = rows.map(row =>
headers.map(h => {
const val = String(row[h] ?? '')
// Экранируем запятые и кавычки
return val.includes(',') || val.includes('"')
? '"' + val.replace(/"/g, '""') + '"'
: val
}).join(',')
)
return BOM + [headerRow, ...dataRows].join('\n')
}
// ===== Тестовые данные =====
const sales = [
{ product: 'Ноутбук', qty: 15, price: 85000, total: 1275000 },
{ product: 'Мышь', qty: 80, price: 1200, total: 96000 },
{ product: 'Клавиатура', qty: 50, price: 3500, total: 175000 },
{ product: 'Монитор', qty: 20, price: 25000, total: 500000 },
]
// CSV экспорт
console.log('=== CSV экспорт ===')
const csvContent = generateCSV(['product', 'qty', 'price', 'total'], sales)
const csvBlob = new Blob([csvContent], { type: 'text/csv; charset=utf-8' })
console.log('CSV размер:', csvBlob.size, 'байт')
console.log('CSV тип:', csvBlob.type)
const csvUrl = URL.createObjectURL(csvBlob)
console.log('URL для скачивания:', csvUrl.substring(0, 20) + '...')
URL.revokeObjectURL(csvUrl)
console.log('URL освобождён')
// JSON экспорт с метаданными
console.log('\n=== JSON экспорт ===')
async function exportJSON(data, meta = {}) {
const payload = {
exportedAt: new Date().toISOString(),
version: '1.0',
...meta,
data,
}
const json = JSON.stringify(payload, null, 2)
const blob = new Blob([json], { type: 'application/json' })
// Читаем обратно для проверки
const text = await blob.text()
const parsed = JSON.parse(text)
console.log('JSON blob размер:', blob.size, 'байт')
console.log('Записей в данных:', parsed.data.length)
console.log('Первый товар:', parsed.data[0].product)
console.log('Экспортирован:', parsed.exportedAt.substring(0, 10))
return blob
}
exportJSON(sales, { source: 'sales-dashboard' }).then(blob => {
console.log('Итоговый размер JSON:', blob.size, 'байт')
})
// ===== slice: чтение по частям =====
console.log('\n=== Blob.slice — чтение по частям ===')
async function readInChunks(blob, chunkSize) {
const chunks = []
let offset = 0
while (offset < blob.size) {
const chunk = blob.slice(offset, offset + chunkSize)
const chunkText = await chunk.text()
chunks.push(chunkText)
offset += chunkSize
}
return chunks.join('')
}
async function sliceDemo() {
const text = 'Строка 1\nСтрока 2\nСтрока 3\nСтрока 4\nСтрока 5'
const blob = new Blob([text])
console.log('Размер blob:', blob.size, 'байт')
// Читаем заголовок (первые 10 байт)
const header = await blob.slice(0, 10).text()
console.log('Первые 10 байт:', JSON.stringify(header))
// Читаем по 15-байтовым чанкам
const assembled = await readInChunks(blob, 15)
console.log('Собрано чанков:', Math.ceil(blob.size / 15))
console.log('Текст восстановлен:', assembled === text)
}
sliceDemo()
// ===== Blob → ArrayBuffer → Blob =====
console.log('\n=== Конвертация Blob ↔ ArrayBuffer ===')
async function convertDemo() {
// Создаём Blob с произвольными бинарными данными
const original = new Uint8Array([0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A])
// Это PNG magic bytes!
const blob = new Blob([original], { type: 'image/png' })
// Blob → ArrayBuffer
const buf = await blob.arrayBuffer()
const bytes = new Uint8Array(buf)
const hex = Array.from(bytes).map(b => b.toString(16).padStart(2, '0')).join(' ')
console.log('PNG magic bytes:', hex)
// 89 50 4e 47 0d 0a 1a 0a
// Проверяем: это PNG?
const isPNG = bytes[0] === 0x89 && bytes[1] === 0x50 &&
bytes[2] === 0x4E && bytes[3] === 0x47
console.log('Это PNG?', isPNG) // true
// ArrayBuffer → новый Blob
const restored = new Blob([buf], { type: blob.type })
console.log('Восстановленный тип:', restored.type) // image/png
console.log('Размер совпадает:', restored.size === blob.size) // true
}
convertDemo()В дашборде аналитики нужно реализовать экспорт данных в разных форматах. Реализуй: - `downloadCSV(data, headers, filename)` — создаёт CSV-Blob из массива объектов (с BOM `\uFEFF` для Excel), логирует параметры скачивания, освобождает URL, возвращает размер файла в байтах - `createReport(title, lines)` — принимает заголовок и массив строк, создаёт Blob `text/plain`, возвращает `{ blob, url, size }` - `blobToBase64(blob)` — конвертирует Blob в base64-строку через ArrayBuffer
downloadCSV: new Blob([csv], {type: "text/csv; charset=utf-8"}), URL.createObjectURL(blob), URL.revokeObjectURL(url), return blob.size. createReport: join строк через \n. blobToBase64: await blob.arrayBuffer(), Uint8Array, fromCharCode, btoa