技术知识文章集合TECHNICAL ARCHIVE · 807 DOCUMENTS

显示模式

登录
ARCHIVE DOCUMENTETC

你不知道的 Blob

所属分类
Other
文件格式
Markdown
原始路径
Other/27-你不知道的 Blob
本文目录5 个章节
  1. 一、Blob 是什么
  2. 二、Blob API 简介
  3. 三、Blob 使用场景
  4. 四、Blob 与 ArrayBuffer 的区别
  5. 整理者注

你不知道的 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。

对比要点:

  1. 除非需要 ArrayBuffer 提供的写入/编辑能力,否则 Blob 格式可能是最好的;
  2. Blob 不可变;ArrayBuffer 可通过 TypedArrays/DataView 操作;
  3. ArrayBuffer 存在内存中可直接操作;Blob 可以位于磁盘、高速缓存内存和其他位置;
  4. Blob 可直接作为参数传递(如 URL.createObjectURL()),但也可能需要 FileReader 等 File API 配合;
  5. 两者可互相转换: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 深度应用"。

807 DOCUMENTS · 12 COLLECTIONS
ARCHIVE SEARCH807 篇文章