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 中包含的数据。