JavaScript Blob Table
| Piece | What it does | Field note |
|---|---|---|
new Blob(parts, {type}) | Build one from strings/Blobs/buffers | Parts concatenate in order; the type option sets the MIME downloads will carry |
blob.size | Size in BYTES | Never compare to string.length - UTF-8 bytes vs UTF-16 units (an emoji: size 3-4, length 1) |
blob.type | The MIME string | What you passed in - empty string if built without a type; the download content-type comes from here |
blob.slice(start, end) | Cut WITHOUT copying | Returns a view over the same bytes - chunked uploads and range reads are just sequential slices |
await blob.text() | Read as string | The modern read (replaces FileReader.readAsText); async, UTF-8 decoded |
await blob.arrayBuffer() | Read as raw buffer | The workspace for typed-array processing; convert back with new Blob([buffer]) |
await blob.stream() | Read piecewise | ReadableStream - process huge files without holding the whole thing in memory |
URL.createObjectURL / revokeObjectURL | The download/display bridge | An url string pointing at the blob in memory - revoke after use or it leaks per click in SPAs |
A Blob (Binary Large Object) is the browser's immutable container for raw bytes: file contents, generated exports, drawn canvas frames, fetched responses. It is NOT a string and NOT a Buffer - it carries bytes plus a MIME type, and everything else (text, array buffer, stream, object URL) is a read you perform on it.
Bottom line: Blobs are immutable and slice() is free - subBlob = blob.slice(start, end) returns a view without copying the data, which is how chunked uploads and range reads work. To let the user download or display one, wrap it in an object URL (URL.createObjectURL) and REVOKE it after use - skipped revocation is the classic memory leak of export-heavy pages.
The honest part: a File IS a Blob - File extends Blob with a name and last-modified date, so every File you get from an input works with every Blob method unchanged. And blob.size is BYTES while string.length is UTF-16 code units: a 3-byte emoji is size 3 (or 4 in UTF-8) but length 1, and conflating them corrupts byte math.
How to use
- Read a Blob as text: await blob.text() - the modern one-liner for file contents (replaces FileReader.readAsText for everything except progress UI).
- Read bytes: await blob.arrayBuffer() when you need typed-array access, or await blob.stream() for piecewise processing of large files without holding it all in memory.
- Trigger a download: const url = URL.createObjectURL(blob); a.href = url; a.download = 'export.csv'; a.click(); URL.revokeObjectURL(url); - revoke in the same tick family, not 'eventually'.
- Cut without copying: blob.slice(0, 1024 * 1024) gives a 1MB chunk view - chunked uploads send slices sequentially with per-chunk retry, and the source blob stays untouched.
- Build one from parts: new Blob([headerRow, dataBlob, footerRow], { type: 'text/csv' }) - strings and Blobs concatenate in order, and the type option sets the MIME the download will carry.
Frequently asked questions
What is the difference between a Blob and a File?
File is a Blob subclass with metadata: name and lastModified. Every Blob method - slice, text, arrayBuffer, stream, object URLs - works identically on both, which is why code written for generated Blobs handles uploaded files with zero changes. The direction to remember: you can construct a File from parts (new File([blob], 'name.png')) but you cannot strip a File back down to a nameless Blob - and functions typed to accept Blobs are the more reusable signature.
Why is my blob.size different from the string length I put in?
Encoding. A Blob stores UTF-8 BYTES; JavaScript strings are UTF-16 code units. The emoji is one code unit (length 1) but three or four UTF-8 bytes (size 3-4); non-Latin scripts diverge similarly. Any byte math - upload limits, chunk sizes, Content-Length headers - must read blob.size, never string length, and the mismatch is the root cause of servers rejecting 'small' uploads that are byte-heavy.
Do I really need revokeObjectURL if the page navigates away soon?
Yes on principle, no in effect for one-shot pages - the URL holds the blob in memory until the DOCUMENT unloads, so a download-and-navigate flow self-cleans. But single-page apps keep the document alive for the session: an export button clicked fifty times without revocation holds fifty blobs until refresh. Revoke right after the click handler (the download has already captured what it needs) and the leak never accumulates.
How is a Blob different from an ArrayBuffer?
A Blob is an opaque FILE-LIKE container (bytes + MIME, storable, downloadable, sliceable); an ArrayBuffer is a raw MEMORY buffer you view and edit through typed arrays. They convert in both directions - blob.arrayBuffer() to process, new Blob([buffer], {type}) to export - and the flow is: fetch or build bytes as ArrayBuffer, manipulate via typed arrays, wrap as Blob to save or display. Neither one is 'better'; they are storage format versus workspace.