Blob 是什么? #2021/11/22

前面讲到了 ArrayBuffer 和 TypedArray 等概念,它们属于 ECMA 标准,是 JavaScript 的一部分。

而在浏览器中,也有一个用于描述二进制数据的对象,即 Blob (Binary large object)。它是一个不可变的包含原始数据的类文件对象。Blob 的主要作用是方便在浏览器中进行文件操作,例如上传,下载,图片展示等等。

Blob 主要包含两个部分,一个可选的字符串类型的 type(通常是 MIME-type)和一个包含 ArrayBuffer / ArrayBufferView / Blob / String 的 blobParts:

创建一个 Blob 的语法如下:

new Blob(blobParts, [options])

blobParts:一个数组,里面可以是 ArrayBuffer, ArrayBufferView, Blob, String,或它们的任意组合。options:可选的对象 type: Blob 中存储的数据的 MIME-type,默认值为空字符串。endings:是否转换行尾以使 Blob 对应于当前操作系统的换行(\r\n 或 \n)。默认情况下是 'transparent' (不做任何操作),但也可以是 'native' (转换)。例如,从 string 创建 Blob:

const blob = new Blob(['...'], { type: 'text/html' })

从 typed array 和 string 创建 Blob:

const hello = new Uint8Array([72, 101, 108, 108, 111]) // 'Hello' 的二进制形式

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

有一点需要注意,我们无法直接更改 Blob 内的数据,但可以从 Blob 中提取片段生成新的 Blob:

blob.slice([byteStart], [byteEnd], [contentType])

byteStart:起始字节位置,默认为 0byteEnd:结束字节位置 (不包含),默认到 blob 末尾contentType:新的 blob 的类型, 默认与原 blob 相同blob.slice 的用法与 array.slice 类似,同样支持负位置索引。

Blob 转 URL #前面说到 Blob 为文件而生,所以可以很容易的将 Blob 使用于 , 或其它接受 url 的标签,用于展示其内容。

这正是因为 Blob 包含了一个 type,用于指定所存储的数据类型 (MIME-type),有了这个类型,我们就可以正确的下载/上传 Blob 对象,也可以通过网络请求发送 Blob,这时 type 会自动成为 Content-Type。

看下面这个例子,用户可以通过点击链接来下载一个包含 'Hello, world' 的文本文件:

Download

也可以通过 JavaScript 动态生成 link,然后通过 link.click() 模拟点击行为,这样无需任何 HTML,可以实现自动下载:

const link = document.createElement('a')

link.download = 'hello.txt'

const blob = new Blob(['Hello, world'], { type: 'text/plain' })

link.href = URL.createObjectURL(blob)

link.click()

URL.revokeObjectURL(link.href)

这里用到了 URL.createObjectURL 和 URL.revokeObjectURL 两个浏览器内置方法。

URL.createObjectURL 接收一个 Blob 对象,并为它生成一个唯一的 URL (blob:/)。

对每个 URL.createObjectURL 生成的 URL,浏览器会在内部存储一个 URL -> Blob 的映射。这样可以通过很简短的 URL 就能访问到 Blob。而且该 URL 只在当前打开的文档中有效,即作用域为当前 session。

但同时这也有一个副作用。由于存在 URL -> Blob 映射, Blob 本身会驻留在内存中。除非页面被卸载,否则浏览器无法自动释放它。这可能不是我们想要的。

通过调用 URL.revokeObjectURL(url) 可以从内部映射中删除对 Blob 的引用,如果 Blob 没有其它引用,它将会被清除,并释放内存。

在上面自动下载的例子中,因为只需要下载一次,所以后面立刻调用了 URL.revokeObjectURL 来释放内存。但在手动点击链接进行下载的例子中,我们希望可以多次下载,所以没有调用 URL.revokeObjectURL,如果这样做,链接上的 Blob url 将会失效,因为它们之间的映射关系已经被移除了。

Blob 转 Base64 #除了可以通过 URL.createObjectURL 生成 Blob url 外,还可以将 Blob 转换成 base64 编码的字符串。

这种编码方式将二进制数据表示为一串安全的“可读”字符。更重要的是,我们可以在 Data URL 中使用这种编码。

一个 Data URL 具有这样的形式:data:[][;base64],。我们可以在任何地方使用这样的 url,就像常规 url 一样。例如

浏览器会解码这个字符串并展示这张图片。

要将 Blob 转换成 base64,可以使用浏览器内置的 FileReader API,它能以多种不同的格式从 Blob 种读取数据。

下面是使用 base64 的形式下载 Blob 的例子:

const link = document.createElement('a')

link.download = 'hello.txt'

const blob = new Blob(['Hello, world!'], { type: 'text/plain' })

const reader = new FileReader()

reader.readAsDataURL(blob) // 以 base64 的格式从 blob 中读取数据

reader.onload = function() {

link.href = reader.result // data url

link.click()

}

这两种创建 Blob URL 的方法都是可行的。但通常 URL.createObjectURL(blob) 更简单而且速度更快。两者对比如下:

URL.createObjectURL(blob)Blob to data url如果对内存泄露比较在意,需要手动 revoke 进行清理无内存泄漏问题,无需手动清理直接访问 Blob,没有 encoding / decoding 过程需要进行 base64 编码,当 Blob 很大时,会有性能问题,而且会占用更大的内存空间为 image 生成 Blob #也可以为图片生成 Blob,然后进行上传和下载等等。

对图片的操作可以借助 canvas 元素实现

使用 canvas.drawImage 将图片画在 canvas 画布上。调用 .toBlob(callback, type, quality) 获取 Blob,等待完成后会执行 callback 回调。// 获取图片元素

const img = document.querySelector('img')

// 创建一个相同尺寸的画布

const canvas = document.createElement('canvas')

canvas.width = img.clientWidth

canvas.height = img.clientHeight

const context = canvas.getContext('2d')

// 复制图片到画布 (还可能通过这个方法对图片进行裁剪)

context.drawImage(img, 0, 0)

// toBlob 是异步操作, callback 会在完成后执行

canvas.toBlob(function(blob) {

// 拿到 blob,进行下载

const link = document.createElement('a')

link.download = 'example.png'

link.href = URL.createObjectURL(blob)

link.click()

// 删除内部 blob 引用, 让浏览器释放内存

URL.revokeObjectURL(link.href)

}, 'image/png')

Blob 转 ArrayBuffer #我们可以使用 Blob() 构造函数从几乎任何东西创建一个 blob,包括 ArrayBuffer 和所有 ArrayBufferView。

但如果我们要行进 low-level 的操作,可以使用 FileReader 获取 Blob 最底层的 ArrayBuffer:

// 从 Blob 获取 ArrayBuffer

const fileReader = new FileReader()

fileReader.readAsArrayBuffer(blob)

fileReader.onload = function(event) {

const arrayBuffer = fileReader.result

}

总结 #ArrayBuffer, Uint8Array 等等其它 ArrayBufferView 都属于二进制数据,而 Blob 代表的是有类型 (type) 的二进制数据。因为 Blob 具有类型,这使得它可以方便地进行上传/下载操作,这些操作在浏览器中非常常见。XMLHttpRequest, fetch web请求原生地支持 Blob 类型。Blob 和 low-level 的二进制数据类型可以进行相互转换。