什么是 Blob,ArrayBuffer,Base64?

5,413 阅读9分钟

从日常需求聊起

前端中后台系统开发过程中,经常会遇到 Excel 表格、图片等文件处理的需求。公司基建如果做得好,那么处理 Excel 下载的需求,你可能仅仅只需要调用一个 download 的方法,返回一个 Blob 文件流即可。又或者在处理某些图片问题时,作为新人的你往往手足无措,img 标签中使用 src 引入图片时出现各种问题。如果你有丰富的知识储备,或者有老人带教,那么就知道,图片除了纯 URL 指定资源地址之外,还可以使用 Base64 等加密字符处理,优点就是图片资源直接使用,而 URL 可能出现加载失败或者异步导致的数据填充失败问题。而 Base64 是直接使用图片转换后的字符串,不存在上述情况。特别是当你存在打印需求时,需要使用字符串模板,并且要在字符串模板中显示图片资源,这时直接使用 Base64 显示图片会安全、稳定得多。

下面,就针对这几种数据类型进行简要讨论。

Blob:二进制长文件

MDN: Blob 对象表示一个不可变、原始数据(二进制数据)的类文件对象。它的数据可以按文本或二进制的格式进行读取,也可以转换成 ReadableStream 来用于数据操作。Blob 表示的不一定是 JavaScript 原生格式的数据。File 接口基于 Blob,继承了 blob 的功能并将其扩展以支持用户系统上的文件。

关键词:不可变、类文件、文本或二进制。

多余而又复杂的概念就不扯了,简单来说你工作中涉及到文件处理的需求中很大概率会用到它。Blob 对象大概就长这样:

image.png

size 表示数据的大小,单位是字节,type 是 MIME 类型的字符串。原型上有 slicestream 等方法:

  • slice(start, end, contentType):返回一个新的 Blob 对象,包含了源 Blob 对象中指定范围内的数据;
  • stream():返回一个能读取 blob 内容的 ReadableStream
  • text():返回一个 Promise 对象且包含 blob 所有内容的 UTF-8 格式的 USVString
  • arrayBuffer():返回一个 Promise 对象且包含 Blob 所有内容的二进制格式的 ArrayBuffer

Ajax 请求响应 Blob 类型

// 原生 Ajax
const downloadBlob = (url, callback) => {
  const xhr = new XMLHttpRequest()
  xhr.open('GET', url)
  xhr.responseType = 'blob'
  xhr.onload = () => {
    callback(xhr.response)
  }
  xhr.send(null)
}

// Axios
axios({
  method:'...',
  url:'...',
  // `responseType` 表示服务器响应的数据类型,可以是 'arraybuffer', 'blob', 'document', 'json', 'text', 'stream'
  responseType: 'blob' 
}).then(res => {
  const {headers, data} = res;
  filename = decodeURI(headers["content-disposition"].split("filename=")[1]);
  blob = new Blob([data], {type: headers["content-type"]});
});

File 对象与 Blob 的关系

File

File 对象代表一个文件,用来读写文件信息。它继承了 Blob 对象,或者说是一种特殊的 Blob 对象,所有可以使用 Blob 对象的场合都可以使用它。

浏览器原生提供一个File()构造函数,用来生成 File 实例对象。

new File(array, name, [options])
  • array:一个数组,成员可以是二进制对象或字符串,表示文件的内容;
  • name:字符串,表示文件名或文件路径;
  • options:配置对象,设置实例的属性。该参数可选。

FileList 对象

FileList 对象是一个类似数组的对象,代表一组选中的文件,每个成员都是一个 File 实例。我们常用的组件库中的 Upload 组件就是基于 <input type="file"> 封装的。

<input type="file">

const files = document.getElementById('input').files;
files instanceof FileList // true

FileReader

FileReader 对象用于读取 File 对象或 Blob 对象所包含的文件内容。

浏览器原生提供一个 FileReader 构造函数,用来生成 FileReader 实例。

const reader = new FileReader();

FileReader 实例属性:

  • FileReader.error:读取文件时产生的错误对象;
  • FileReader.readyState:整数,表示读取文件时的当前状态。一共有三种可能的状态,0 表示尚未加载任何数据,1 表示数据正在加载,2 表示加载完成;
  • FileReader.result:读取完成后的文件内容,有可能是字符串,也可能是一个 ArrayBuffer 实例;
  • FileReader.onabort:abort 事件(用户终止读取操作)的监听函数;
  • FileReader.onerror:error 事件(读取错误)的监听函数;
  • FileReader.onload:load 事件(读取操作完成)的监听函数,通常在这个函数里面使用 result 属性,拿到文件内容;
  • FileReader.onloadstart:loadstart 事件(读取操作开始)的监听函数;
  • FileReader.onloadend:loadend 事件(读取操作结束)的监听函数;
  • FileReader.onprogress:progress 事件(读取操作进行中)的监听函数。

提到 FileFileReader 主要在于 FileReader 能够读取 Blob 对象内容,还可以将其转换为 ArrayBuffer 格式,利于后期各类数据类型间的互转。

js 代码创建 Blob 对象

const strArray = ['123456789'];
const blob = new Blob(strArray);

在 js 中,允许我们通过构造函数 Blob 来创建 Blob 对象。从代码中可以看到我们传递了一个字符串数组作为它的第一个参数。

MDN: const blob = new Blob(array, options); 参数 array 是一个由ArrayBufferArrayBufferViewBlobDOMString 等对象构成的 Array ,或者其他类似对象的混合体,它将会被放进 Blob。DOMStrings 会被编码为 UTF-8。参数 options 是一个可选的BlobPropertyBag字典,它可能会指定如下两个属性:type,默认值为 "",它代表了将会被放入到 blob 中的数组内容的 MIME 类型。endings,默认值为"transparent",用于指定包含行结束符\n的字符串如何被写入。 它是以下两个值中的一个:"native",代表行结束符会被更改为适合宿主操作系统文件系统的换行符,或者 "transparent",代表会保持 blob 中保存的结束符不变

总之,我们可以知道 Blob 是可以利用代码生成的,同时,它的第一个参数是一个 array 。并且 ArrayBufferBlob 参数的一种。那么由此,我们引出 ArrayBuffer

ArrayBuffer:通用定长的原始二进制数据缓冲区

MDN: ArrayBuffer 对象用来表示通用的、固定长度的原始二进制数据缓冲区。它是一个字节数组,通常在其他语言中称为“byte array”。你不能直接操作 ArrayBuffer 的内容,而是要通过类型数组对象或 DataView 对象来操作,它们会将缓冲区中的数据表示为特定的格式,并通过这些格式来读写缓冲区的内容。

同样的,使用构造函数看创建 ArrayBuffer 对象:

new ArrayBuffer(length)

其中,参数 length 表示要创建的 buffer 对象的长度,由于 ArrayBuffer 的特点是定长,这个参数不可省略。length 在正负 2**53 范围内。

image.png

可以看到,ArrayBuffer 原型上有一些类似 Uint8Array 的对象,实际上,ArrayBuffer 是不能直接被操作的,需要通过类型数组对象 TypedArray 或者 DataView 对象来操作,它们能将缓冲区中的数据表示为特定的格式,并通过这些格式来读写缓冲区内容。

TypedArray 格式类型:

Int8Array(); 
Uint8Array(); 
Uint8ClampedArray(); 
Int16Array(); 
Uint16Array(); 
Int32Array(); 
Uint32Array(); 
Float32Array(); 
Float64Array();

DataView 则是指自定义的解释器,暂且不管它。拿 Uint8Array 举个例子,Uint8Array 表示把 ArrayBuffer 的每个字节(byte,8-bit)当成一个无符号整型数,值的范围是 0-255。有什么用呢?我们可以通过 String.fromCharCode 将这些数值对应的 Unicode 编码拼接成数据字符串。进而使用这个字符串再进行 Base64 编码,处理我们的图片数据。这也正是我们开头说的,确实存在实际需求会用到这类数据类型。

const arr = new Uint8Array([12, 27, 38, 99, 122]); 
console.log(arr.length); // 5
console.log(arr[1]); // 27

Base64:二进制编码字符串

MDN: Base64 是一组相似的二进制到文本(binary-to-text)的编码规则,使得二进制数据在解释成 radix-64 的表现形式后能够用 ASCII 字符串的格式表示出来。Base64 这个词出自一种 MIME 数据传输编码

同样的,Base64 也有对应的方法进行创建。但是,Base64 实际上是一种编码方式,算不上数据类型,存在编码,就存在解码。分别是:window.atob(string)window.btoa(string)

window.atob() 函数能够解码通过 Base64 编码的字符串数据。相反地,window.btoa() 函数能够从二进制数据“字符串”创建一个 Base64 编码的 ASCII 字符串。

前面讲到 ArrayBuffer 时,由 Uint8Array 创建的二进制数据字符串正好可以作为 btoa() 的参数从而编码成 Base64 字符串。这样,可以直接在 img 标签中这么使用:

<img src="data:image/png;base64,******" />

其中的 ****** 表示图片编码后的 Base64 字符串。

进阶:三种文件类型互转

说了这么多,其实都是铺垫。实际工作中的运用,都是在这几种数据类型间相互转换。

Blob 转 ArrayBuffer

const reader = new FileReader();
reader.onload = function (result) {
  // result.target.result 就是 ArrayBuffer 对象
}
reader.readerAsArrayBuffer(blob)

ArrayBuffer 转 Base64

const base64String = window.btoa(String.fromCharCode(...new Uint8Array(buffer)))

解析:

  • window.btoa 专门用于将普通字符串转换为 Base64 编码的字符串;
  • Uint8Array 前面有讲解,是一个数组;
  • String.fromCharCode 将单个 Uint8Array 数值对应的 Unicode 转为普通字符串;

Blob 转 Base64

前面 Blob 转 ArrayBuffer 及 ArrayBuffer 转 Base64,那么顺其自然的,Blob 转 Base64 也是由这两部分组成。

// 传入一个回调函数拿到转换后的 Base64 字符串
const blobToBase64 = (blob, callback) => {
  const reader = new FileReader();
  reader.onload = function (result) {
    // result.target.result 就是 ArrayBuffer 对象
    const base64String = window.btoa(String.fromCharCode(...new Uint8Array(result.target.result)))
    callback(base64String)
  }
  reader.readerAsArrayBuffer(blob)
}

当然了,还可以反向转换,这部分就留给大家自行实现。

总结

关于 BlobFileBase64 的应用真的很多,本文仅仅是浅尝辄止,讲了一些皮毛,能应对工作中的一些基础问题。知之为知之,知道一个东西,用到的时候能想起,并能解决实际问题就是好的。列了很多概念,可能看起来不是很顺畅,但是了解过后一定会有很大收获。

回到开头,如果你有下载 Excel 的需求,但是公司封装的 download 方法已经很好用了,那你了解 Blob 后便可以阅读它的源码,了解它的实现,当然了,这也需要后端配合。如果你们没有类似实现,那么你现在也有了一个大概的思路,Excel 后端本质就是返回文件流,至于前端怎么处理,至少 Blob 是需要用到的,搜一下就有很多实现可以参考。

另外,关于图片处理等常见需求,在必要时转换为 Base64 会有很好的效果,但是并不是所有情况都适用 Base64,因为经过 Base64 编码后的文件资源一般会增加 1/3,除非一些必须以字符显示图片数据的场景,不然 Base64 不见得是最佳方案。