ArrayBuffer
和视图是 ECMA 标准的一部分,是 JavaScript 的一部分。
在浏览器中,还有其他更高层次的对象,在 文件 API 中描述,特别是 Blob
。
Blob
由一个可选的字符串 type
(通常是 MIME 类型)和 blobParts
组成,blobParts
是一个包含其他 Blob
对象、字符串和 BufferSource
的序列。
构造函数语法如下:
new Blob(blobParts, options);
blobParts
是一个包含Blob
/BufferSource
/String
值的数组。options
可选对象type
–Blob
类型,通常是 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
– 新Blob
的type
,默认与源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)` 更简单、更快。
- 如果关心内存,我们需要撤销它们。
- 直接访问 blob,没有“编码/解码”
- 无需撤销任何内容。
- 对大型 `Blob` 对象进行编码会导致性能和内存损失。
图像到 blob
我们可以创建一个图像、图像部分甚至页面截图的 `Blob`。这对于将其上传到某个地方非常方便。
图像操作通过 `<canvas>` 元素完成
- 使用 canvas.drawImage 在画布上绘制图像(或其部分)。
- 调用画布方法 .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);
}
总结
虽然 ArrayBuffer
、Uint8Array
和其他 BufferSource
是“二进制数据”,但 Blob 代表“带有类型的二进制数据”。
这使得 Blob 对于浏览器中常见的上传/下载操作非常方便。
执行网络请求的方法,例如 XMLHttpRequest、fetch 等等,可以与 Blob
本机一起使用,以及其他二进制类型。
我们可以轻松地在 Blob
和低级二进制数据类型之间进行转换。
- 我们可以使用
new Blob(...)
构造函数从类型化数组创建Blob
。 - 我们可以使用
blob.arrayBuffer()
从 Blob 获取ArrayBuffer
,然后创建对它的视图以进行低级二进制处理。
当我们需要处理大型 Blob 时,转换流非常有用。您可以轻松地从 Blob 创建 ReadableStream
。Blob
接口的 stream()
方法返回一个 ReadableStream
,它在读取时返回 Blob 中包含的数据。
评论
<code>
标签,对于多行代码,请使用<pre>
标签,对于超过 10 行的代码,请使用沙箱(plnkr,jsbin,codepen…)