深入理解Blob、ArrayBuffer、FileReader、Object URL、Base64
1.Blob
- 全称为 binary large object ,即二进制大对象。
不可修改
。- 从中读取内容的唯一方式是使用 FileReader。
MDN 中对 Blob 的解释:
Blob 对象表示一个不可变、原始数据的类文件对象。它的数据可以按文本或二进制的格式进行读取,也可以转换成 ReadableStream 来用于数据操作。
1. 创建
new Blob(array, options)
其有两个参数:
array
:由ArrayBuffer
、ArrayBufferView
、Blob
、DOMString
等对象构成的,将会被放进Blob
;options
:可选的BlobPropertyBag
字典,它可能会指定如下两个属性type
:默认值为 "",表示将会被放入到blob
中的数组内容的 MIME 类型。endings
:默认值为"transparent
",用于指定包含行结束符\n
的字符串如何被写入,不常用。
MIME 类型 | 描述 |
---|---|
text/plain | 纯文本文档 |
text/html | html 文档 |
text/javascript | javascript 文档 |
text/css | css 文档 |
application/json | json 文件 |
application/pdf | pdf 文件 |
application/xml | xml 文件 |
image/jpeg | jpeg 图像 |
image/png | png 图像 |
image/gif | gif 图像 |
image/svg+xml | svg 图像 |
audio/mpeg | mp3 文件 |
video/mpeg | mp4 文件 |
下面来看一个简单的例子:
const blob = new Blob(['Hello World'], { type: 'text/plain' })
console.log(blob.size) // 11
console.log(blob.type) // "text/plain"
size
:Blob 对象中所包含数据的大小(字节数)ps: 1字节(byte)=8 位(bit) 1024 字节=1K 1024k=1M;type
:字符串,认为该 Blob 对象所包含的 MIME 类型。如果类型未知,则为空字符串。
2.实例方法
arrayBuffer()
方法返回一个Promise
对象,包含 blob 中的数据,并在ArrayBuffer
中以二进制
数据的形式呈现。
blob.arrayBuffer().then(buffer => /* 处理 ArrayBuffer 数据的代码……*/);
var buffer = await blob.arrayBuffer();
FileReader.readAsArrayBuffer()
这个方法与之类似,但arrayBuffer()
返回一个 promise 对象,而不是像FileReader
一样返回一个基于事件的 API。
Blob.slice()
方法用于创建一个包含源Blob
的指定字节范围内的数据的新Blob
对象。也就是将 blob 对象切片。
var blob = instanceOfBlob.slice([start [, end [, contentType]]]};
start
:Blob
里的起始下标,支持复数。它的默认值是 0,如果你传入的 start 的长度大于源Blob
的长度,那么返回的将会是一个长度为 0 并且不包含任何数据的一个Blob
对象。end
:Blob
里的起始下标,支持复数。contentType
: 给新的Blob
赋予一个新的文档类型。这将会把它的 type 属性设为被传入的值。它的默认值是一个空的字符串
2.File
- 特殊类型的 Blob。
- 可以用在任意的 Blob 类型的 context 中。
- Blob 的属性和方法都可以用于 File 对象。
- File 继承自 Blob。
注意:File 对象中只存在于浏览器环境中,在 Node.js 环境中不存在。
在 JavaScript 中,主要有两种方法来获取 File 对象:
<input type='file'>
元素上选择文件后返回的 FileList 对象;- 文件拖放操作生成的
DataTransfer
对象;
(1) 使用 input 获取
FileList:
(2)文件拖放
另一种获取 File 对象的方式就是拖放 API,就是将浏览器之外的文件拖到浏览器窗口中,并将它放在一个称为拖放区域的特殊区域中。拖放区域用于响应放置操作并从放置的项目中提取信息。这些是通过 ondrop
和 ondragover
两个 API 实现的。
下面来看一个简单的例子,首先定义一个拖放区域:
<div id="drop-zone"></div>
然后给这个元素添加 ondragover
和 ondrop
事件处理程序:
const dropZone = document.getElementById('drop-zone')
dropZone.ondragover = (e) => {
e.preventDefault()
}
dropZone.ondrop = (e) => {
e.preventDefault()
const files = e.dataTransfer.files
console.log(files)
}
注意:这里给两个 API 都添加了 e.preventDefault()
,用来阻止默认事件。它是非常重要的,可以用来阻止浏览器的一些默认行为,比如放置文件将显示在浏览器窗口中。
当拖放文件到拖放区域时,控制台就会输出一个 FileList 数组,该数组的每一个元素都是一个 File
对象。这个 FileList 数组是从事件参数的 dataTransfer
属性的 files
获取的:
可以看到,这里得到的 File
对象和通过 input
标签获得的 File
对象是完全一样的。
3.FileReader
FileReader 是一个异步 API,用于读取文件并提取其内容以供进一步使用。FileReader 可以将 Blob 读取为不同的格式。
注意: FileReader 仅用于以安全的方式从用户(远程)系统读取文件内容,不能用于从文件系统中按路径名简单地读取文件。
(1)基本使用
可以使用 FileReader 构造函数来创建一个 FileReader 对象:
const reader = new FileReader()
error
:表示在读取文件时发生的错误;result
:文件内容。该属性仅在读取操作完成后才有效,数据的格式取决于使用哪个方法来启动读取操作。readyState
:表示FileReader
状态的数字。取值如下:常量名 值 描述 EMPTY 0 还没有加载任何数据 LOADING 1 数据正在被加载 DONE 2 已完成全部的读取请求
readAsText()
:读取指定 Blob 中的内容,完成之后,result
属性中将包含一个字符串以表示所读取的文件内容。读取文本文件时,就是比较正常的。如果读取二进制文件往往会产生乱码readAsDataURL()
:读取指定 Blob 中的内容,完成之后,result
属性中将包含一个data: URL
格式的 Base64 字符串以表示所读取文件的内容。用于读取二进制文件readAsArrayBuffer()
:读取指定 Blob 中的内容,完成之后,result
属性中保存的将是被读取文件的ArrayBuffer
数据对象;readAsBinaryString()
:读取指定 Blob 中的内容,完成之后,result
属性中将包含所读取文件的原始二进制数据;
可以看到,上面这些方法都接受一个要读取的 blob 对象作为参数,读取完之后会将读取的结果放入对象的 result
属性中。
(2)事件处理
FileReader 对象常用的事件如下:
onabort
:该事件在读取操作被中断时触发;onerror
:该事件在读取操作发生错误时触发;onload
:该事件在读取操作完成时触发;onprogress
:该事件在读取 Blob 时触发。
当上传大文件时,可以通过 progress
事件来监控文件的读取进度:
const reader = new FileReader();
reader.onprogress = (e) => {
if (e.loaded && e.total) {
const percent = (event.loaded / event.total) * 100;
console.log(`上传进度: ${Math.round(percent)} %`);
}
});
progress
事件提供了两个属性:loaded
(已读取量)和total
(需读取总量)。
4. ArrayBuffer
ArrayBuffer 对象用来表示通用的、固定长度的原始二进制数据缓冲区。ArrayBuffer 的内容不能直接操作,只能通过 DataView 对象或 TypedArrray 对象来访问。这些对象用于读取和写入缓冲区内容。
ArrayBuffer() 构造函数创建一个以字节为单位的给定长度的新 ArrayBuffer
ArrayBuffer 本身就是一个黑盒,不能直接读写所存储的数据,需要借助以下视图对象来读写:
- TypedArray:用来生成内存的视图,通过 9 个构造函数,可以生成 9 种数据格式的视图。
- DataViews:用来生成内存的视图,可以自定义格式和字节序。
TypedArray 视图和 DataView 视图的区别主要是字节序,前者的数组成员都是同一个数据类型,后者的数组成员可以是不同的数据类型。 (PS: 类似 TS 中数组与元组的区别)
那 ArrayBuffer 与 Blob 有啥区别呢?
- 根据 ArrayBuffer 和 Blob 的特性,Blob 作为一个整体文件,适合用于传输;当需要对二进制数据进行操作时(比如要修改某一段数据时),就可以使用 ArrayBuffer。
- Blob 通过 arrayBuffer()方法得到 ArrayBuffer
TypedArray 对象一共提供 9 种类型的视图,每一种视图都是一种构造函数。如下:
元素 | 类型化数组 | 每个元素所占字节 | 描述 |
---|---|---|---|
Int8 | Int8Array | 1 | 8 位有符号整数 |
Uint8 | Uint8Array | 1 | 8 位无符号整数 |
Uint8C | Uint8ClampedArray | 1 | 8 位无符号整数 |
Int16 | Int16Array | 2 | 16 位有符号整数 |
Uint16 | Uint16Array | 2 | 16 位无符号整数 |
Int32 | Int32Array | 4 | 32 位有符号整数 |
Uint32 | Uint32Array | 4 | 32 位无符号整数 |
Float32 | Float32Array | 4 | 32 位浮点 |
Float64 | Float64Array | 8 | 64 位浮点 |
不同视图代表着 TypedArray 中一个元素占多少字节和其他意义 来看看这些都是什么意思:
- Uint8Array: 将 ArrayBuffer 中的每个字节视为一个整数,可能的值从 0 到 255 (一个字节等于 8 位)。 这样的值称为“8 位无符号整数”。
- Uint16Array:将 ArrayBuffer 中任意两个字节视为一个整数,可能的值从 0 到 65535。 这样的值称为“16 位无符号整数”。
- Uint32Array:将 ArrayBuffer 中任何四个字节视为一个整数,可能值从 0 到 4294967295,这样的值称为“32 位无符号整数”。
这些构造函数生成的对象统称为 TypedArray 对象。它们和正常的数组很类似,都有 length 属性,都能用索引获取数组元素,所有数组的方法都可以在类型化数组上面使用。
那类型化数组和数组有什么区别呢?
- 类型化数组的元素都是连续的,不会为空;
- 类型化数组的所有成员的类型和格式相同;
- 类型化数组元素默认值为 0;
- 类型化数组本质上只是一个视图层,不会存储数据,数据都存储在更底层的 ArrayBuffer 对象中。
1.创建
① new ArrayBuffer()
ArrayBuffer 可以通过以下方式生成:
new ArrayBuffer(bytelength)
ArrayBuffer()
构造函数可以分配指定字节数量的缓冲区,其参数和返回值如下:
- 参数:它接受一个参数,即 bytelength,表示要创建数组缓冲区的大小(以字节为单位。);
- 返回值:返回一个新的指定大小的 ArrayBuffer 对象,内容初始化为 0。
2.读取
使用 TypedArray
使用 DataView
DataView 视图是一个可以从 二进制 ArrayBuffer 对象中读写多种数值类型的底层接口,使用它时,不用考虑不同平台的字节序问题。
DataView 视图提供更多操作选项,而且支持设定字节序。本来,在设计目的上,ArrayBuffer 对象的各种 TypedArray 视图,是用来向网卡、声卡之类的本机设备传送数据,所以使用本机的字节序就可以了;而 DataView 视图的设计目的,是用来处理网络设备传来的数据,所以大端字节序或小端字节序是可以自行设定的。
DataView 视图可以通过构造函数来创建,它的参数是一个 ArrayBuffer 对象,生成视图。其语法如下:
new DataView(buffer [, byteOffset [, byteLength]])
其有三个参数:
buffer
:一个已经存在的 ArrayBuffer 对象,DataView 对象的数据源。byteOffset
:可选,此 DataView 对象的第一个字节在 buffer 中的字节偏移。如果未指定,则默认从第一个字节开始。byteLength
:可选,此 DataView 对象的字节长度。如果未指定,这个视图的长度将匹配 buffer 的长度。
来看一个例子:
buffer
:返回对应的 ArrayBuffer 对象;byteLength
:返回占据的内存字节长度;byteOffset
:返回当前视图从对应的 ArrayBuffer 对象的哪个字节开始。
读取内存
DataView 实例提供了以下方法来读取内存,它们的参数都是一个字节序号,表示开始读取的字节位置:
- getInt8:读取 1 个字节,返回一个 8 位整数。
- getUint8:读取 1 个字节,返回一个无符号的 8 位整数。
- getInt16:读取 2 个字节,返回一个 16 位整数。
- getUint16:读取 2 个字节,返回一个无符号的 16 位整数。
- getInt32:读取 4 个字节,返回一个 32 位整数。
- getUint32:读取 4 个字节,返回一个无符号的 32 位整数。
- getFloat32:读取 4 个字节,返回一个 32 位浮点数。
- getFloat64:读取 8 个字节,返回一个 64 位浮点数。
下面来看一个例子:
const buffer = new ArrayBuffer(24)
const view = new DataView(buffer)
// 从第1个字节读取一个8位无符号整数
const view1 = view.getUint8(0)
// 从第2个字节读取一个16位无符号整数
const view2 = view.getUint16(1)
// 从第4个字节读取一个16位无符号整数
const view3 = view.getUint16(3)
写入内存
DataView 实例提供了以下方法来写入内存,它们都接受两个参数,第一个参数表示开始写入数据的字节序号,第二个参数为写入的数据:
- setInt8:写入 1 个字节的 8 位整数。
- setUint8:写入 1 个字节的 8 位无符号整数。
- setInt16:写入 2 个字节的 16 位整数。
- setUint16:写入 2 个字节的 16 位无符号整数。
- setInt32:写入 4 个字节的 32 位整数。
- setUint32:写入 4 个字节的 32 位无符号整数。
- setFloat32:写入 4 个字节的 32 位浮点数。
- setFloat64:写入 8 个字节的 64 位浮点数。
5. Object URL
Object URL(MDN 定义名称)又称 Blob URL(W3C 定义名称),是 HTML5 中的新标准。它是一个用来表示 File Object 或 Blob Object 的 URL。在网页中,我们可能会看到过这种形式的 Blob URL:
其实 Blob URL/Object URL 是一种伪协议,允许将 Blob
和 File
对象用作图像、二进制数据下载链接等的 URL 源。
对于 Blob/File 对象,可以使用 URL 构造函数的 URL.createObjectURL()
方法创建将给出的对象的 URL。这个 URL 对象表示指定的 File 对象或 Blob 对象。我们可以在<img>
、<script>
标签中或者 <a>
和 <link>
标签的 href 属性中使用这个 URL。
那这个 API 有什么意义呢?可以将 Blob/File 对象转化为 URL,通过这个 URL 就可以实现文件下载或者图片显示等。
当我们使用createObjectURL()
方法创建一个 data URL 时,就需要使用revokeObjectURL()
方法从内存中清除它来释放内存。虽然浏览器会在文档卸载时自动释放 Data URL,但为了提高性能,我们应该使用revokeObjectURL()
来手动释放它。revokeObjectURL()
方法接受一个 Data URL 作为其参数,返回undefined
。下面来看一个例子:
const objUrl = URL.createObjectURL(new File([''], 'filename'))
console.log(objUrl)
URL.revokeObjectURL(objUrl)
Base64
Base64 是一种基于 64 个可打印字符来表示二进制数据的表示方法。Base64 编码普遍应用于需要通过被设计为处理文本数据的媒介上储存和传输二进制数据而需要编码该二进制数据的场景。这样是为了保证数据的完整并且不用在传输过程中修改这些数据。
在 JavaScript 中,有两个函数被分别用来处理解码和编码 base64 字符串:
atob()
:解码,解码一个 Base64 字符串;btoa()
:编码,从一个字符串或者二进制数据编码一个 Base64 字符串。
btoa('JavaScript') // 'SmF2YVNjcmlwdA=='
atob('SmF2YVNjcmlwdA==') // 'JavaScript'
主要使用
- 使用 toDataURL()方法把 canvas 画布内容生成 base64 编码格式的图片:
const canvas = document.getElementById('canvas')
const ctx = canvas.getContext('2d')
const dataUrl = canvas.toDataURL()
- 使用 readAsDataURL()方法把上传的文件转为 base64 格式的 data URI,比如上传头像展示或者编辑:
<input type="file" id="fileInput" />
<img id="preview" />
const fileInput = document.getElementById('fileInput')
const preview = document.getElementById('preview')
const reader = new FileReader()
fileInput.onchange = (e) => {
reader.readAsDataURL(e.target.files[0])
}
reader.onload = (e) => {
preview.src = e.target.result
console.log(e.target.result)
}
- 另外,一些小的图片都可以使用 base64 格式进行展示,
img
标签和background
的url
属性都支持使用 base64 格式的图片,这样做也可以减少 HTTP 请求。
7. 格式转化
看完这些基本的概念,下面就来看看常用格式之间是如何转换的。
(1)ArrayBuffer → blob
const blob = new Blob([new Uint8Array(buffer, byteOffset, length)])
(2)ArrayBuffer → base64
const base64 = btoa(String.fromCharCode.apply(null, new Uint8Array(arrayBuffer)))
(3)base64 → blob
const base64toBlob = (base64Data, contentType, sliceSize) => {
const byteCharacters = atob(base64Data)
const byteArrays = []
for (let offset = 0; offset < byteCharacters.length; offset += sliceSize) {
const slice = byteCharacters.slice(offset, offset + sliceSize)
const byteNumbers = new Array(slice.length)
for (let i = 0; i < slice.length; i++) {
byteNumbers[i] = slice.charCodeAt(i)
}
const byteArray = new Uint8Array(byteNumbers)
byteArrays.push(byteArray)
}
const blob = new Blob(byteArrays, { type: contentType })
return blob
}
const dataURLtoBlob = function (dataurl) {
var arr = dataurl.split(',')
var mime = arr[0].match(/:(.*?);/)[1]
var bstr = atob(arr[1])
var n = bstr.length
var u8arr = new Uint8Array(n)
while (n--) {
u8arr[n] = bstr.charCodeAt(n)
}
return new Blob([u8arr], { type: mime })
}
(4)blob → ArrayBuffer
function blobToArrayBuffer(blob) {
return new Promise((resolve, reject) => {
const reader = new FileReader()
reader.onload = () => resolve(reader.result)
reader.onerror = () => reject
reader.readAsArrayBuffer(blob)
})
}
(5)blob → base64
function blobToBase64(blob) {
return new Promise((resolve) => {
const reader = new FileReader()
reader.onloadend = () => resolve(reader.result)
reader.readAsDataURL(blob)
})
}
(6)blob → Object URL
const objectUrl = URL.createObjectURL(blob)