你不知道的 Blob
Category(分类): Other Status: 已读
原文:你不知道的 Blob(博客园,转载自公众号"全栈修仙之路",阿宝哥)。 本文为整理版本,保留全部 API 讲解与六大使用场景的代码示例。
如果你允许用户从你的网站下载某些文件,那你可能会遇到 Blob 类型。很多开发者熟练使用 API 之后并不会继续思考 Blob 是什么——遇到棘手问题时就束手无策。熟悉 API 的使用之后多问几个为什么,不仅能加深理解,还能触类旁通。
一、Blob 是什么
Blob(Binary Large Object)表示二进制类型的大对象。在数据库管理系统中,将二进制数据存储为一个单一个体的集合,通常是影像、声音或多媒体文件。
在 JavaScript 中,Blob 类型的对象表示不可变的类似文件对象的原始数据。 Blob 对象含有两个属性:size(数据大小,字节)和 type(MIME 类型字符串)。Blob 表示的不一定是 JavaScript 原生格式的数据——比如 File 接口就是基于 Blob,继承其功能并扩展为支持用户系统上的文件。
二、Blob API 简介
Blob 由一个可选的字符串 type(通常是 MIME 类型)和 blobParts 组成。
构造函数
var aBlob = new Blob(blobParts, options);
- blobParts:由 ArrayBuffer、ArrayBufferView、Blob、DOMString 等对象构成的数组(DOMStrings 会被编码为 UTF-8);
- options:
type:默认"",放入 blob 的内容的 MIME 类型;endings:默认"transparent",指定含\n的字符串如何写入——"native"表示改为宿主操作系统的换行符,"transparent"表示保持不变。
从字符串创建:
let myBlobParts = ['<html><h2>Hello Semlinker</h2></html>'];
let myBlob = new Blob(myBlobParts, { type: 'text/html', endings: "transparent" });
console.log(myBlob.size + " bytes size"); // 37 bytes size
console.log(myBlob.type + " is the type"); // text/html is the type
从类型化数组和字符串创建:
let hello = new Uint8Array([72, 101, 108, 108, 111]); // 二进制格式的 "hello"
let blob = new Blob([hello, ' ', 'semlinker'], { type: 'text/plain' });
属性与方法
size(只读):数据大小(字节);type(只读):数据的 MIME 类型,未知则为空字符串;slice([start[, end[, contentType]]]):返回包含源 Blob 指定范围数据的新 Blob;stream():返回能读取 blob 内容的 ReadableStream;text():返回 Promise,包含全部内容的 UTF-8 格式字符串;arrayBuffer():返回 Promise,包含全部内容的二进制 ArrayBuffer。
Blob 对象是不可改变的:不能直接更改其中的数据,但可以分割、组合创建新的 Blob——类似 JavaScript 字符串的不可变性。
三、Blob 使用场景
3.1 分片上传
File 对象是特殊类型的 Blob。大文件传输场景用 slice 切割后分片上传:
const file = new File(["a".repeat(1000000)], "test.txt");
const chunkSize = 40000;
const url = "https://httpbin.org/post";
async function chunkedUpload() {
for (let start = 0; start < file.size; start += chunkSize) {
const chunk = file.slice(start, start + chunkSize + 1);
const fd = new FormData();
fd.append("data", chunk);
await fetch(url, { method: "post", body: fd }).then((res) => res.text());
}
}
3.2 从互联网下载数据
XHR 方式(responseType = 'blob')或 fetch 方式(response.blob()):
const downloadBlob = (url, callback) => {
const xhr = new XMLHttpRequest();
xhr.open('GET', url);
xhr.responseType = 'blob';
xhr.onload = () => callback(xhr.response);
xhr.send(null);
};
// fetch 获取线上图片并显示
fetch(new Request('flowers.jpg'))
.then(function (response) { return response.blob(); })
.then(function (myBlob) {
let objectURL = URL.createObjectURL(myBlob);
myImage.src = objectURL;
});
3.3 Blob 用作 URL
Blob URL / Object URL 是一种伪协议,允许 Blob 和 File 对象用作图像、下载链接等的 URL。URL.createObjectURL 为 Blob 创建唯一 URL,形式为 blob:<origin>/<uuid>:
blob:https://example.org/40a5fb5a-d56d-4a33-b4e2-0acf6a8e5f641
浏览器内部为每个生成的 URL 存储「URL → Blob」映射。URL 仅在当前文档打开的状态下有效,引用它的 Blob 若已不存在则访问会 404。
内存副作用:映射存在期间 Blob 本身驻留内存,浏览器无法释放。文档卸载时自动清除,但长寿命应用中不会很快发生——所以不再需要时应调用 URL.revokeObjectURL(url) 删除引用、释放内存。
Blob 文件下载示例:
const download = (fileName, blob) => {
const link = document.createElement("a");
link.href = URL.createObjectURL(blob);
link.download = fileName;
link.click();
link.remove();
URL.revokeObjectURL(link.href);
};
downloadBtn.addEventListener("click", (event) => {
const fileName = "blob.txt";
const myBlob = new Blob(["一文彻底掌握 Blob Web API"], { type: "text/plain" });
download(fileName, myBlob);
});
3.4 Blob 转换为 Base64
URL.createObjectURL 的替代方案是把 Blob 转为 base64 字符串。Base64 常用于处理文本数据的场合表示、传输、存储二进制数据(MIME 邮件、XML 复杂数据)。
Data URLs 由四部分组成:前缀 data:、MIME 类型、可选的 base64 标记、数据本身,可用于把图片等二进制数据以文本字符串嵌入网页(减少网络请求)。注意:大图、色彩层次丰富的图片不适合——base64 后字符串非常大,会增大 HTML 影响加载速度。
用 FileReader 实现图片本地预览(File → Data URL):
<input type="file" accept="image/*" onchange="loadFile(event)">
<img id="output"/>
const loadFile = function (event) {
const reader = new FileReader();
reader.onload = function () {
document.querySelector('#output').src = reader.result;
};
reader.readAsDataURL(event.target.files[0]);
};
提交 Data URL 到服务器时,服务端(Express 示例)需做解码处理:
app.post('/upload', function (req, res) {
let imgData = req.body.imgData;
let base64Data = imgData.replace(/^data:image\/\w+;base64,/, "");
let dataBuffer = Buffer.from(base64Data, 'base64');
fs.writeFile("image.png", dataBuffer, function (err) {
err ? res.send(err) : res.send("图片上传成功!");
});
});
FileReader 还提供 readAsArrayBuffer()、readAsText() 方法转换成其他格式。
3.5 图片压缩
上传前先在前端压缩图片,减少传输数据量。核心是 Canvas 的 toDataURL(type, encoderOptions)(type 默认 image/png;encoderOptions 是质量,仅对 image/jpeg、image/webp 有效,取值 0-1,越界用默认 0.92):
const MAX_WIDTH = 800; // 图片最大宽度
function compress(base64, quality, mimeType) {
let canvas = document.createElement("canvas");
let img = document.createElement("img");
img.crossOrigin = "anonymous";
return new Promise((resolve, reject) => {
img.src = base64;
img.onload = () => {
let targetWidth, targetHeight;
if (img.width > MAX_WIDTH) {
targetWidth = MAX_WIDTH;
targetHeight = (img.height * MAX_WIDTH) / img.width;
} else {
targetWidth = img.width;
targetHeight = img.height;
}
canvas.width = targetWidth;
canvas.height = targetHeight;
let ctx = canvas.getContext("2d");
ctx.clearRect(0, 0, targetWidth, targetHeight);
ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
resolve(canvas.toDataURL(mimeType, quality / 100));
};
});
}
Data URL 转 Blob 再提交(进一步减少传输量):
function dataUrlToBlob(base64, mimeType) {
let bytes = window.atob(base64.split(",")[1]);
let ab = new ArrayBuffer(bytes.length);
let ia = new Uint8Array(ab);
for (let i = 0; i < bytes.length; i++) ia[i] = bytes.charCodeAt(i);
return new Blob([ab], { type: mimeType });
}
function uploadFile(url, blob) {
let formData = new FormData();
formData.append("image", blob);
let request = new XMLHttpRequest();
request.open("POST", url, true);
request.send(formData);
}
Canvas 还有异步的
toBlob(callback, mimeType, qualityArgument),回调第一个参数即转换好的 blob。
3.6 生成 PDF 文档
浏览器端用 jsPDF 生成 PDF:
(function generatePdf() {
const doc = new jsPDF();
doc.text("Hello semlinker!", 66, 88);
const blob = new Blob([doc.output()], { type: "application/pdf" });
blob.text().then((blobAsText) => console.log(blobAsText)); // %PDF-1.3 ...
})();
jsPDF 也支持生成带图片的 PDF(doc.addImage(imgData, 'JPEG', 15, 40, 180, 160))。
四、Blob 与 ArrayBuffer 的区别
- ArrayBuffer:通用的、固定长度的原始二进制数据缓冲区。不能直接操纵内容,需通过 TypedArrays 或 DataView 读写;
- Blob:不可变的类文件原始数据,不一定是 JS 原生格式。File 接口基于 Blob。
对比要点:
- 除非需要 ArrayBuffer 提供的写入/编辑能力,否则 Blob 格式可能是最好的;
- Blob 不可变;ArrayBuffer 可通过 TypedArrays/DataView 操作;
- ArrayBuffer 存在内存中可直接操作;Blob 可以位于磁盘、高速缓存内存和其他位置;
- Blob 可直接作为参数传递(如
URL.createObjectURL()),但也可能需要 FileReader 等 File API 配合; - 两者可互相转换:FileReader 的
readAsArrayBuffer()把 Blob 转 ArrayBuffer;new Blob([new Uint8Array(data)])把 ArrayBuffer 转 Blob。
AJAX 场景下的用法(二选一):
xhr.responseType = 'arraybuffer'; // 之后 new Uint8Array(xhr.response)
xhr.responseType = "blob"; // 之后 new Blob([xhr.response]) 或直接用
整理者注
Blob 的知识地图:构造(blobParts + type)→ 不可变性 → 与 ArrayBuffer/File 的关系 → Blob URL(内存副作用与 revoke)→ Base64/Data URL 互转 → 实战场景。与《为什么视频网站的视频链接地址是blob?》对照阅读:本文是"通用基础",那篇是"MediaSource 深度应用"。