一、Blob 是什么
Blob(Binary Large Object)是浏览器中表示不可变的原始二进制数据的对象。它可以存储任意类型的数据(图片、文件、文本、音视频等),是浏览器文件处理的核心接口。
Blob 与相关类型的关系
1 2 3 4 5 6 7 8 9
| 字符串/String ↓ new TextEncoder() ArrayBuffer ← → Uint8Array / Int8Array / ... ↓ new Blob() Blob ↓ new File() (继承自 Blob) File ← 用户上传 / File API ↓ Object URL / Base64 / FormData(上传)
|
二、创建 Blob
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| const blob = new Blob(['<h1>Hello</h1>'], { type: 'text/html' });
const buffer = new ArrayBuffer(8); const blob2 = new Blob([buffer]);
const parts = [ '<html><head><title>Test</title></head><body>', '<h1>Hello</h1>', '</body></html>', ]; const htmlBlob = new Blob(parts, { type: 'text/html' });
|
MIME 类型
1 2 3 4 5 6 7 8 9 10 11 12 13
| { type: 'text/plain' } { type: 'text/html' } { type: 'text/css' } { type: 'application/json' } { type: 'application/pdf' } { type: 'application/zip' } { type: 'image/png' } { type: 'image/jpeg' } { type: 'image/webp' } { type: 'audio/mpeg' } { type: 'video/mp4' } { type: 'application/octet-stream' }
|
三、读取 Blob
方式一:转为文本
1 2 3 4 5
| const blob = new Blob(['Hello, 世界!'], { type: 'text/plain' });
const text = await blob.text(); console.log(text);
|
方式二:转为 ArrayBuffer
1 2 3
| const arrayBuffer = await blob.arrayBuffer(); const uint8Array = new Uint8Array(arrayBuffer); console.log(uint8Array);
|
方式三:转为 Base64 Data URL
1 2 3 4 5 6 7 8
| const reader = new FileReader();
reader.onload = () => { console.log(reader.result); };
reader.readAsDataURL(blob);
|
方式四:转为 Object URL
1 2 3 4 5 6 7 8 9 10
| const url = URL.createObjectURL(blob);
img.src = url; iframe.src = url; a.href = url;
URL.revokeObjectURL(url);
|
方式五:转为 Stream
1 2 3 4 5 6 7 8 9 10
| const stream = blob.stream(); const reader = stream.getReader();
async function read() { const { done, value } = await reader.read(); if (!done) { console.log(value); read(); } }
|
四、Blob 属性与方法
1 2 3 4 5 6 7 8 9 10 11
| const blob = new Blob(['Hello World'], { type: 'text/plain' });
blob.size; blob.type;
const chunk1 = blob.slice(0, 5); const chunk2 = blob.slice(6, 11);
const chunk = blob.slice(0, 5, 'text/plain');
|
五、File(继承自 Blob)
File 是 Blob 的子类,扩展了文件名和最后修改时间属性:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| <input type="file" id="fileInput" multiple> <script> document.getElementById('fileInput').addEventListener('change', (e) => { const files = e.target.files; const file = files[0]; console.log(file.name); console.log(file.size); console.log(file.type); console.log(file.lastModified); }); </script>
const file = new File( ['{"name":"Alice"}'], 'user.json', { type: 'application/json' } );
dropZone.addEventListener('drop', (e) => { const files = e.dataTransfer.files; });
|
Blob vs File
1 2 3 4
| file instanceof Blob; file.text(); file.slice();
|
六、Blob URL vs Data URL
1 2 3 4 5 6 7 8 9 10
| const blob = new Blob(['<h1>Hello</h1>'], { type: 'text/html' }); const blobUrl = URL.createObjectURL(blob);
const dataUrl = 'data:text/html;base64,' + btoa('<h1>Hello</h1>');
|
| 特性 | Blob URL | Data URL |
|---|
| 长度 | 固定(约 50 字节) | 随数据增长(比原数据大 37%) |
| 请求 | 不发起 HTTP 请求 | 不发起 HTTP 请求 |
| 内存 | 引用浏览器内存中的 Blob | 编码在 URL 字符串中 |
| 释放 | 需手动 revokeObjectURL | 随页面关闭自动释放 |
| 适用 | 大文件(图片、视频) | 小文件(图标、短文本) |
七、实际应用场景
场景一:前端生成文件并下载
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| function downloadJSON(data, filename = 'data.json') { const blob = new Blob([JSON.stringify(data, null, 2)], { type: 'application/json', }); const url = URL.createObjectURL(blob);
const a = document.createElement('a'); a.href = url; a.download = filename; a.click();
URL.revokeObjectURL(url); }
downloadJSON({ id: 1, name: 'Alice' }, 'user.json');
|
场景二:图片压缩预览
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| <input type="file" accept="image/*" id="upload"> <img id="preview">
<script> document.getElementById('upload').addEventListener('change', async (e) => { const file = e.target.files[0]; const compressedBlob = await compressImage(file, 0.5); const url = URL.createObjectURL(compressedBlob); document.getElementById('preview').src = url; });
function compressImage(file, quality = 0.7) { return new Promise((resolve) => { const img = new Image(); img.onload = () => { const canvas = document.createElement('canvas'); canvas.width = img.width / 2; canvas.height = img.height / 2; const ctx = canvas.getContext('2d'); ctx.drawImage(img, 0, 0, canvas.width, canvas.height); canvas.toBlob((blob) => resolve(blob), file.type, quality); }; img.src = URL.createObjectURL(file); }); } </script>
|
场景三:大文件分片上传
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| async function uploadFile(file) { const chunkSize = 1024 * 1024; const totalChunks = Math.ceil(file.size / chunkSize);
for (let i = 0; i < totalChunks; i++) { const start = i * chunkSize; const end = Math.min(start + chunkSize, file.size); const chunk = file.slice(start, end);
const formData = new FormData(); formData.append('chunk', chunk, file.name); formData.append('index', i); formData.append('total', totalChunks);
await fetch('/api/upload', { method: 'POST', body: formData }); } }
|
场景四:动态创建脚本/样式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| const code = ` self.onmessage = (e) => { const result = e.data.map(x => x * 2); self.postMessage(result); }; `; const blob = new Blob([code], { type: 'application/javascript' }); const worker = new Worker(URL.createObjectURL(blob));
worker.postMessage([1, 2, 3]); worker.onmessage = (e) => console.log(e.data);
const cssBlob = new Blob([` .dynamic-style { color: red; font-size: 20px; } `], { type: 'text/css' }); const link = document.createElement('link'); link.rel = 'stylesheet'; link.href = URL.createObjectURL(cssBlob); document.head.appendChild(link);
|
场景五:Canvas 导出为图片
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| const canvas = document.getElementById('myCanvas');
canvas.toBlob((blob) => { const url = URL.createObjectURL(blob); }, 'image/png');
const dataUrl = canvas.toDataURL('image/jpeg', 0.8); const byteString = atob(dataUrl.split(',')[1]); const ab = new ArrayBuffer(byteString.length); const ia = new Uint8Array(ab); for (let i = 0; i < byteString.length; i++) { ia[i] = byteString.charCodeAt(i); } const blob = new Blob([ab], { type: 'image/jpeg' });
|
八、Blob 与 ArrayBuffer 的对比
| 特性 | Blob | ArrayBuffer |
|---|
| 可变性 | 不可变 | 可通过 TypedArray 修改 |
| 编码 | 固定编码(MIME 类型) | 纯二进制,无编码信息 |
| 读取 | text()、arrayBuffer()、stream() | 需通过 DataView 或 TypedArray |
| 用途 | 文件、上传、下载、URL | WebGL、音频处理、加密、WebSocket |
1 2 3 4 5 6 7 8 9
| const buffer = await blob.arrayBuffer();
const blob = new Blob([buffer], { type: 'application/octet-stream' });
const uint8 = new Uint8Array([72, 101, 108, 108, 111]); const textBlob = new Blob([uint8], { type: 'text/plain' });
|
九、兼容性
所有现代浏览器均完整支持 Blob、File、FileReader、Blob URL。IE 10+ 支持基本功能但不支持 blob.text()、blob.stream() 等 Promise 方法。
| API | 支持情况 |
|---|
new Blob() | 所有浏览器 |
File | IE 10+ |
FileReader | IE 10+ |
URL.createObjectURL | IE 10+ |
blob.text() / blob.arrayBuffer() | Chrome 76+, Safari 14.1+ |
blob.stream() | Chrome 76+, Safari 14.1+ |
十、常见问题
Q1: createObjectURL 什么时候需要 revoke
每次 createObjectURL 都会占用内存映射。如果不需要了应立即 revokeObjectURL。对于图片预览,在 img.onload 后即可 revoke:
1 2 3 4 5 6
| const url = URL.createObjectURL(file); const img = new Image(); img.onload = () => { URL.revokeObjectURL(url); }; img.src = url;
|
Q2: blob.text() 和 FileReader 有什么区别
blob.text() 是异步方法,返回 Promise,更简洁。FileReader 基于事件回调,可读性较差。推荐使用 blob.text() 和 blob.arrayBuffer()。
Q3: 如何检测一个对象是不是 Blob
1 2
| blob instanceof Blob; Blob.prototype.isPrototypeOf(blob);
|
十一、推荐学习路径
- 掌握 Blob 的创建和读取(
text、arrayBuffer、stream) - 理解 Blob URL 和 Data URL 的区别
- 结合 File 对象做文件上传预览
- 学会
Blob.slice 实现大文件分片 - 结合 Canvas 做图片压缩和导出