ArrayBuffer
разом з об’єктами представлень, як частина JavaScript, описані в ECMA стандарті.
Також в браузерах, специфікацією File API, визначено додаткові високорівневі об’єкти для роботи з даними, зокрема Blob
.
Blob
складається з необов’язкового рядку type
(зазвичай MIME тип) та blobParts
– послідовності інших Blob
об’єктів, рядків та BufferSource
.
Приклад синтаксису конструктору:
new Blob(blobParts, options);
blobParts
масив, що може містити значення типуBlob
/BufferSource
/String
.options
необов’язковий об’єкт з властивостями:type
– рядок, що дозволяє додати тип дляBlob
, переважно використовуються MIME тип, наприклад,image/png
,endings
– визначає чи потрібно привести всі символи нового рядку, при створенніBlob
, у відповідність до формату поточної операційної система (\r\n
або\n
). Типове значення"transparent"
(не вносити змін), але якщо потрібно внести зміни, то необхідно вказати"native"
.
Наприклад:
// створення Blob з рядку
let blob = new Blob(["<html>…</html>"], {type: 'text/html'});
// зверніть увагу: першим аргументом повинен бути масив [...]
// створення Blob з типізованого масиву і рядку
let hello = new Uint8Array([72, 101, 108, 108, 111]); // "Hello" у бінарному форматі
let blob = new Blob([hello, ' ', 'world'], {type: 'text/plain'});
Отримати зріз з Blob
можна наступним чином:
blob.slice([byteStart], [byteEnd], [contentType]);
byteStart
– початковий байт, типове значення 0.byteEnd
– останній байт (не включно, типово до кінця).contentType
– визначаєtype
нового об’єкту, типове значення буде таким же, як у початкових даних.
Аргументи такі ж самі, як у array.slice
, від’ємні числа теж можна використовувати.
Blob
об’єкти незмінніДані в Blob
не можуть бути зміненими, але ми можемо зробити зріз з їх частини, створити новий Blob
з них, змішати їх в новий Blob
і так далі.
Поведінка відповідає рядкам в JavaScript: ми не можемо змінити якийсь символ в рядку, але ми можемо створити новий.
Blob як URL
Blob можна використати як URL для показу вмісту HTML тегів <a>
, <img>
та інших.
Blob
об’єкти можна легко завантажити/вивантажити, під час запиту в заголовку Content-Type
буде використано значення поля type
.
Розгляньмо простий приклад. Після кліку на посилання, ви завантажите динамічно згенерований Blob
, як файл з вмістом hello world
:
<!-- атрибут download змішує браузер завантажити вміст замість відкриття сторінки -->
<a download="hello.txt" href='#' id="link">Download</a>
<script>
let blob = new Blob(["Hello, world!"], {type: 'text/plain'});
link.href = URL.createObjectURL(blob);
</script>
Посилання також можна створити динамічно в JavaScript та імітувати клік за допомогою link.click()
, після чого завантаження розпочнеться автоматично.
Ось приклад коду, що дозволяє користувачу завантажити динамічно створений Blob
без HTML:
let link = document.createElement('a');
link.download = 'hello.txt';
let blob = new Blob(['Hello, world!'], {type: 'text/plain'});
link.href = URL.createObjectURL(blob);
link.click();
URL.revokeObjectURL(link.href);
URL.createObjectURL
отримує аргументом Blob
та створює унікальний URL для нього у форматі blob:<origin>/<uuid>
.
Ось як виглядає значення link.href
:
blob:https://javascript.info/1e67e00e-860d-40a5-89ae-6ab0cbee6273
Для кожного URL, що створено за допомогою URL.createObjectURL
, браузер зберігає відображення URL → Blob
. Тому такі URL короткі, але дозволяють отримати доступ до Blob
.
Згенерований URL, разом з посиланням на нього, існує тільки всередині поточної сторінки, доки вона відкрита. Це дозволяє посилатися на Blob
в тегах <img>
, <a>
або будь-якому об’єкті, що очікує URL.
Але це спричиняє побічний ефект. Доки існує посилання на Blob
у відображенні, Blob
повинен залишатися в пам’яті. Браузер не може вивільнити пам’ять, що зайнята Blob
.
Відображення автоматично очищується, коли сторінка закривається. Тому, якщо потрібно, щоб застосунок довго був активний – очищення пам’яті може трапитися нескоро.
Тому після створення URL Blob
буде залишатися в пам’яті навіть, коли вже не потрібен.
URL.revokeObjectURL(url)
видаляє посилання у внутрішньому відображенні, що дозволяє видалити Blob
(якщо немає інших посилань на нього) і звільнити пам’ять.
В останньому прикладі, ми цілеспрямовано одразу викликаємо URL.revokeObjectURL(link.href)
, бо очікуємо, що Blob
буде завантажено один раз.
У попередньому прикладі, з HTML посиланням, на яке можна клікнути, ми не викликаємо URL.revokeObjectURL(link.href)
, бо це зробить Blob
не доступним. Після видалення посилання на Blob
з відображення URL більше не спрацює.
Blob в base64
Іншим способом отримати доступ до Blob
, замість URL.createObjectURL
, є перетворення Blob
в base64 закодований рядок.
Це кодування дозволяє представити дані як рядок ASCII символів від 0 до 64. І, що найважливіше, ми можемо використовувати це кодування в “data-urls”.
Data url має формат data:[<mediatype>][;base64],<data>
. Ми можемо використовувати такі посилання будь-де, як і “звичайні”.
Наприклад, смайлик:
<img src="data:image/png;base64,R0lGODlhDAAMAKIFAF5LAP/zxAAAANyuAP/gaP///wAAAAAAACH5BAEAAAUALAAAAAAMAAwAAAMlWLPcGjDKFYi9lxKBOaGcF35DhWHamZUW0K4mAbiwWtuf0uxFAgA7">
Браузер розкодує рядок та покаже зображення:
Для перетворення Blob
в base64 ми будемо використовувати вбудований об’єкт FileReader
. Він може читати дані з Blob
в різних форматах. В наступному розділі next chapter ми глибше з ним познайомимось.
Демонстрація завантаження Blob
за допомогою base64:
let link = document.createElement('a');
link.download = 'hello.txt';
let blob = new Blob(['Hello, world!'], {type: 'text/plain'});
let reader = new FileReader();
reader.readAsDataURL(blob); // перетворить Blob в base64 та викличе onload
reader.onload = function() {
link.href = reader.result; // data url
link.click();
};
Обидва способи створення URL з Blob
доступні для використання. Але, переважно, URL.createObjectURL(blob)
простіше та швидше.
- Необхідно видаляти посилання на об’єкт для звільнення пам’яті.
- Безпосередній доступ до
Blob
без проміжного “закодування/розкодування”.
- Немає потреби звільняти посилання на об’єкти.
- Втрати швидкодії та навантаження на пам’ять у разі кодування великих
Blob
об’єктів.
Зображення в Blob
Також ми можемо створити Blob
із зображення, його частини чи навіть зі скріншоту сторінки. Це стане у нагоді, якщо кудись потрібно завантажити світлину.
Робота з зображеннями відбувається за допомогою елементу <canvas>
:
- canvas.drawImage використовується для показу зображення.
- Виклик canvas методу .toBlob(callback, format, quality) створює
Blob
та виконуєcallback
, після закінчення.
В наступному прикладі, зображення тільки копіюється, але, за потреби, ми можемо обрізати чи трансформувати його перед створенням Blob
:
// беремо будь-яке зображення
let img = document.querySelector('img');
// створюємо <canvas> такого ж розміру
let canvas = document.createElement('canvas');
canvas.width = img.clientWidth;
canvas.height = img.clientHeight;
let context = canvas.getContext('2d');
// копіюємо в нього зображення (цей метод дозволяє вирізати частину зображення)
context.drawImage(img, 0, 0);
// наприклад, можна викликати context.rotate() або багато інших операцій
// toBlob -- це асинхронна операція, передану функцію буде викликано після готовності
canvas.toBlob(function(blob) {
// Blob готовий, завантажуємо його
let link = document.createElement('a');
link.download = 'example.png';
link.href = URL.createObjectURL(blob);
link.click();
// видаляємо внутрішнє посилання на Blob, щоб браузер міг звільнити пам’ять
URL.revokeObjectURL(link.href);
}, 'image/png');
Якщо ви надаєте перевагу async/await
синтаксису замість функцій зворотнього виклику:
let blob = await new Promise(resolve => canvasElem.toBlob(resolve, 'image/png'));
Для створення знімків екрану можна використовувати бібліотеку https://github.com/niklasvh/html2canvas. Вона просто обходить сторінку і малює її в <canvas>
. Потім ми можемо отримати Blob
як показано вище.
Blob в ArrayBuffer
Конструктор Blob
дозволяє створювати Blob
майже з будь-чого, тим паче з BufferSource
.
Але якщо нам потрібно виконати низькорівневу обробку, ми можемо отримати найнижчий рівень ArrayBuffer
з blob.arrayBuffer()
:
// отримати arrayBuffer з blob
const bufferPromise = await blob.arrayBuffer();
// or
blob.arrayBuffer().then(buffer => /* process the ArrayBuffer */);
Від Blob до потоку
Коли ми читаємо та пишемо в blob дані розміром понад 2 ГБ
, використання arrayBuffer
стає більш інтенсивним для нас. На цьому етапі ми можемо безпосередньо перетворити blob на потік.
Потік – це спеціальний об’єкт, який дозволяє читати з нього (або записувати в нього) частина за частиною. Це виходить за рамки цієї статті, але ось приклад, і ви можете прочитати більше на сторінці https://developer.mozilla.org/en-US/docs/Web/API/Streams_API. Потоки зручні для даних, які придатні для обробки частинами.
Метод stream()
інтерфейсу Blob
повертає ReadableStream
, який під час читання повертає дані, що містяться в Blob
.
Прочитати з нього ми можемо, наприклад, ось так:
// отримати readableStream з blob
const readableStream = blob.stream();
const stream = readableStream.getReader();
while (true) {
// для кожної ітерації: дані є наступним фрагментом(частиною) blob
let { done, value } = await stream.read();
if (done) {
// більше немає даних у потоці
console.log('all blob processed.');
break;
}
// зробити щось із частиною даних, яку ми щойно прочитали з blob
console.log(value);
}
Підсумки
Якщо ArrayBuffer
, Uint8Array
та інші BufferSource
представляють просто “бінарні дані”, то Blob є “типізованими бінарними даними”.
Це робить Blob
зручним для вивантаження/завантаження, що часто потрібно робити в браузері.
Методи, що виконують запити, як-от XMLHttpRequest, fetch та інші, можуть безпосередньо працювати з Blob
, як і з іншими типами бінарних даних.
Ми можемо легко трансформувати дані між Blob
та іншими низькорівневими бінарними типами:
- Ми можемо створити
Blob
з типізованих масивів з використанням конструкторуnew Blob(...)
. - Ми можемо повернути
ArrayBuffer
з Blob за допомогоюblob.arrayBuffer()
, а потім створити над ним подання для низькорівневої двійкової обробки.
Потоки перетворення дуже корисні, коли нам потрібно обробляти великий blob. Ви можете легко створити ReadableStream
з blob. Метод stream()
інтерфейсу Blob
повертає ReadableStream
, який після читання повертає дані, що містяться у цьому blob.