2022年5月16日

Blob

ArrayBuffer 和视图是 ECMA 标准的一部分,是 JavaScript 的一部分。

在浏览器中,还有其他更高层次的对象,在 文件 API 中描述,特别是 Blob

Blob 由一个可选的字符串 type(通常是 MIME 类型)和 blobParts 组成,blobParts 是一个包含其他 Blob 对象、字符串和 BufferSource 的序列。

构造函数语法如下:

new Blob(blobParts, options);
  • blobParts 是一个包含 Blob/BufferSource/String 值的数组。
  • options 可选对象
    • typeBlob 类型,通常是 MIME 类型,例如 image/png
    • endings – 是否将行尾符转换为与当前操作系统换行符(\r\n\n)相对应的 Blob。默认值为 "transparent"(不做任何操作),但也可以是 "native"(转换)。

例如

// create Blob from a string
let blob = new Blob(["<html>…</html>"], {type: 'text/html'});
// please note: the first argument must be an array [...]
// create Blob from a typed array and strings
let hello = new Uint8Array([72, 101, 108, 108, 111]); // "Hello" in binary form

let blob = new Blob([hello, ' ', 'world'], {type: 'text/plain'});

我们可以使用以下方法提取 Blob 片段:

blob.slice([byteStart], [byteEnd], [contentType]);
  • byteStart – 起始字节,默认值为 0。
  • byteEnd – 最后一个字节(不包含,默认值为结尾)。
  • contentType – 新 Blobtype,默认与源 Blob 相同。

这些参数类似于 array.slice,也允许使用负数。

Blob 对象是不可变的

我们无法直接更改 Blob 中的数据,但可以切片 Blob 的部分,从这些片段创建新的 Blob 对象,将它们混合到新的 Blob 中等等。

这种行为类似于 JavaScript 字符串:我们无法更改字符串中的字符,但可以创建一个新的更正后的字符串。

Blob 作为 URL

Blob 可以轻松用作 <a><img> 或其他标签的 URL,以显示其内容。

由于 type 的存在,我们还可以下载/上传 Blob 对象,type 自然会成为网络请求中的 Content-Type

让我们从一个简单的例子开始。通过单击链接,您可以下载一个动态生成的 Blob,其中包含 hello world 内容,作为文件。

<!-- download attribute forces the browser to download instead of navigating -->
<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.js.cn/1e67e00e-860d-40a5-89ae-6ab0cbee6273

对于每个由 URL.createObjectURL 生成的 URL,浏览器都会在内部存储一个 URL → Blob 映射。因此,这些 URL 很短,但允许访问 Blob

生成的 URL(以及包含它的链接)仅在当前文档打开时有效。它允许在 <img><a> 中引用 Blob,基本上任何其他需要 URL 的对象都可以使用它。

不过,它有一个副作用。当存在 Blob 的映射时,Blob 本身驻留在内存中。浏览器无法释放它。

映射会在文档卸载时自动清除,因此 Blob 对象会在那时被释放。但是,如果应用程序是长期运行的,那么这种情况不会很快发生。

因此,如果我们创建一个 URL,该 `Blob` 将会挂在内存中,即使不再需要它。

URL.revokeObjectURL(url) 从内部映射中删除引用,从而允许删除 `Blob`(如果没有任何其他引用),并释放内存。

在最后一个示例中,我们希望 `Blob` 只使用一次,用于立即下载,因此我们立即调用 `URL.revokeObjectURL(link.href)`。

在之前使用可点击 HTML 链接的示例中,我们没有调用 `URL.revokeObjectURL(link.href)`,因为这会使 `Blob` URL 无效。撤销后,由于映射被删除,URL 就不再起作用了。

Blob 到 base64

URL.createObjectURL 的替代方法是将 `Blob` 转换为 base64 编码的字符串。

该编码将二进制数据表示为一个由 0 到 64 的 ASCII 码组成的超安全“可读”字符的字符串。更重要的是,我们可以在“数据 URL”中使用这种编码。

一个 数据 URL 的格式为 data:[<mediatype>][;base64],<data>。我们可以像使用“普通”URL 一样在任何地方使用这种 URL。

例如,这里有一个笑脸

<img src="data:image/png;base64,R0lGODlhDAAMAKIFAF5LAP/zxAAAANyuAP/gaP///wAAAAAAACH5BAEAAAUALAAAAAAMAAwAAAMlWLPcGjDKFYi9lxKBOaGcF35DhWHamZUW0K4mAbiwWtuf0uxFAgA7">

浏览器将解码字符串并显示图像:

要将 `Blob` 转换为 base64,我们将使用内置的 `FileReader` 对象。它可以以多种格式读取 Blob 中的数据。在 下一章 中,我们将更深入地介绍它。

以下是通过 base-64 下载 blob 的演示

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); // converts the blob to base64 and calls onload

reader.onload = function() {
  link.href = reader.result; // data url
  link.click();
};

这两种创建 `Blob` URL 的方法都可用。但通常 `URL.createObjectURL(blob)` 更简单、更快。

URL.createObjectURL(blob)
  • 如果关心内存,我们需要撤销它们。
  • 直接访问 blob,没有“编码/解码”
Blob 到数据 URL
  • 无需撤销任何内容。
  • 对大型 `Blob` 对象进行编码会导致性能和内存损失。

图像到 blob

我们可以创建一个图像、图像部分甚至页面截图的 `Blob`。这对于将其上传到某个地方非常方便。

图像操作通过 `<canvas>` 元素完成

  1. 使用 canvas.drawImage 在画布上绘制图像(或其部分)。
  2. 调用画布方法 .toBlob(callback, format, quality),该方法会创建一个 `Blob`,并在完成后使用它运行 `callback`。

在下面的示例中,图像只是被复制,但我们可以在将其转换为 Blob 之前,在画布上对其进行裁剪或转换。

// take any image
let img = document.querySelector('img');

// make <canvas> of the same size
let canvas = document.createElement('canvas');
canvas.width = img.clientWidth;
canvas.height = img.clientHeight;

let context = canvas.getContext('2d');

// copy image to it (this method allows to cut image)
context.drawImage(img, 0, 0);
// we can context.rotate(), and do many other things on canvas

// toBlob is async operation, callback is called when done
canvas.toBlob(function(blob) {
  // blob ready, download it
  let link = document.createElement('a');
  link.download = 'example.png';

  link.href = URL.createObjectURL(blob);
  link.click();

  // delete the internal blob reference, to let the browser clear memory from it
  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

但是,如果我们需要执行低级处理,我们可以从 blob.arrayBuffer() 获取最低级的 ArrayBuffer

// get arrayBuffer from blob
const bufferPromise = await blob.arrayBuffer();

// or
blob.arrayBuffer().then(buffer => /* process the ArrayBuffer */);

从 Blob 到流

当我们读写超过 2 GB 的 Blob 时,使用 arrayBuffer 会变得更加内存密集。此时,我们可以直接将 Blob 转换为流。

流是一个特殊的对象,允许我们逐部分地读取(或写入)它。这超出了我们在这里的范围,但这里有一个示例,您可以在 https://mdn.org.cn/en-US/docs/Web/API/Streams_API 中了解更多信息。流对于适合逐部分处理的数据非常方便。

Blob 接口的 stream() 方法返回一个 ReadableStream,它在读取时返回 Blob 中包含的数据。

然后我们可以像这样从它读取数据

// get readableStream from blob
const readableStream = blob.stream();
const stream = readableStream.getReader();

while (true) {
  // for each iteration: value is the next blob fragment
  let { done, value } = await stream.read();
  if (done) {
    // no more data in the stream
    console.log('all blob processed.');
    break;
  }

   // do something with the data portion we've just read from the blob
  console.log(value);
}

总结

虽然 ArrayBufferUint8Array 和其他 BufferSource 是“二进制数据”,但 Blob 代表“带有类型的二进制数据”。

这使得 Blob 对于浏览器中常见的上传/下载操作非常方便。

执行网络请求的方法,例如 XMLHttpRequestfetch 等等,可以与 Blob 本机一起使用,以及其他二进制类型。

我们可以轻松地在 Blob 和低级二进制数据类型之间进行转换。

  • 我们可以使用 new Blob(...) 构造函数从类型化数组创建 Blob
  • 我们可以使用 blob.arrayBuffer() 从 Blob 获取 ArrayBuffer,然后创建对它的视图以进行低级二进制处理。

当我们需要处理大型 Blob 时,转换流非常有用。您可以轻松地从 Blob 创建 ReadableStreamBlob 接口的 stream() 方法返回一个 ReadableStream,它在读取时返回 Blob 中包含的数据。

教程地图

评论

在评论之前请阅读…
  • 如果您有改进建议,请提交 GitHub 问题或拉取请求,而不是评论。
  • 如果您不理解文章中的某些内容,请详细说明。
  • 要插入少量代码,请使用<code>标签,对于多行代码,请使用<pre>标签,对于超过 10 行的代码,请使用沙箱(plnkrjsbincodepen…)