blog

深入理解Blob、ArrayBuffer、FileReader、Object URL、Base64

2025-04-29

relation.png

1.Blob

  • 全称为 binary large object ,即二进制大对象。
  • 不可修改
  • 从中读取内容的唯一方式是使用 FileReader。

    MDN 中对 Blob 的解释:
    Blob 对象表示一个不可变、原始数据的类文件对象。它的数据可以按文本或二进制的格式进行读取,也可以转换成 ReadableStream 来用于数据操作。

1. 创建

new Blob(array, options)

其有两个参数:

  • array:由  ArrayBufferArrayBufferViewBlobDOMString  等对象构成的,将会被放进  Blob
  • options:可选的  BlobPropertyBag  字典,它可能会指定如下两个属性
    type:默认值为 "",表示将会被放入到  blob  中的数组内容的 MIME 类型。
    endings:默认值为"transparent",用于指定包含行结束符\n的字符串如何被写入,不常用。
MIME 类型描述
text/plain纯文本文档
text/htmlhtml 文档
text/javascriptjavascript 文档
text/csscss 文档
application/jsonjson 文件
application/pdfpdf 文件
application/xmlxml 文件
image/jpegjpeg 图像
image/pngpng 图像
image/gifgif 图像
image/svg+xmlsvg 图像
audio/mpegmp3 文件
video/mpegmp4 文件

下面来看一个简单的例子:

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]]]};
  • startBlob里的起始下标,支持复数。它的默认值是 0,如果你传入的 start 的长度大于源Blob的长度,那么返回的将会是一个长度为 0 并且不包含任何数据的一个Blob对象。
  • endBlob里的起始下标,支持复数。
  • 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:

image.png

(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  获取的:

image.png

可以看到,这里得到的  File  对象和通过  input  标签获得的  File  对象是完全一样的。



3.FileReader

FileReader 是一个异步 API,用于读取文件并提取其内容以供进一步使用。FileReader 可以将 Blob 读取为不同的格式。

注意: FileReader 仅用于以安全的方式从用户(远程)系统读取文件内容,不能用于从文件系统中按路径名简单地读取文件。

(1)基本使用

可以使用 FileReader 构造函数来创建一个 FileReader 对象:

const reader = new FileReader()

  • error:表示在读取文件时发生的错误;
  • result:文件内容。该属性仅在读取操作完成后才有效,数据的格式取决于使用哪个方法来启动读取操作。
  • readyState:表示FileReader状态的数字。取值如下:
    常量名描述
    EMPTY0还没有加载任何数据
    LOADING1数据正在被加载
    DONE2已完成全部的读取请求

  • 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:用来生成内存的视图,可以自定义格式和字节序。

image.png

TypedArray 视图和 DataView 视图的区别主要是字节序,前者的数组成员都是同一个数据类型,后者的数组成员可以是不同的数据类型。 (PS: 类似 TS 中数组与元组的区别)

那 ArrayBuffer 与 Blob 有啥区别呢?

  • 根据 ArrayBuffer 和 Blob 的特性,Blob 作为一个整体文件,适合用于传输;当需要对二进制数据进行操作时(比如要修改某一段数据时),就可以使用 ArrayBuffer。
  • Blob 通过 arrayBuffer()方法得到 ArrayBuffer

TypedArray 对象一共提供 9 种类型的视图,每一种视图都是一种构造函数。如下:

元素类型化数组每个元素所占字节描述
Int8Int8Array18 位有符号整数
Uint8Uint8Array18 位无符号整数
Uint8CUint8ClampedArray18 位无符号整数
Int16Int16Array216 位有符号整数
Uint16Uint16Array216 位无符号整数
Int32Int32Array432 位有符号整数
Uint32Uint32Array432 位无符号整数
Float32Float32Array432 位浮点
Float64Float64Array864 位浮点

不同视图代表着 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

image.png

使用 DataView

DataView 视图是一个可以从 二进制 ArrayBuffer 对象中读写多种数值类型的底层接口,使用它时,不用考虑不同平台的字节序问题。

DataView 视图提供更多操作选项,而且支持设定字节序。本来,在设计目的上,ArrayBuffer 对象的各种 TypedArray 视图,是用来向网卡、声卡之类的本机设备传送数据,所以使用本机的字节序就可以了;而 DataView 视图的设计目的,是用来处理网络设备传来的数据,所以大端字节序或小端字节序是可以自行设定的。

DataView 视图可以通过构造函数来创建,它的参数是一个 ArrayBuffer 对象,生成视图。其语法如下:

new DataView(buffer [, byteOffset [, byteLength]])

其有三个参数:

  • buffer:一个已经存在的 ArrayBuffer 对象,DataView 对象的数据源。
  • byteOffset:可选,此 DataView 对象的第一个字节在 buffer 中的字节偏移。如果未指定,则默认从第一个字节开始。
  • byteLength:可选,此 DataView 对象的字节长度。如果未指定,这个视图的长度将匹配 buffer 的长度。

来看一个例子:

image.png

  • 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:

image.png

其实 Blob URL/Object URL 是一种伪协议,允许将 BlobFile 对象用作图像、二进制数据下载链接等的 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'

主要使用

  1. 使用 toDataURL()方法把 canvas 画布内容生成 base64 编码格式的图片:
const canvas = document.getElementById('canvas')
const ctx = canvas.getContext('2d')
const dataUrl = canvas.toDataURL()
  1. 使用 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)
}
  1. 另外,一些小的图片都可以使用 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)
CD..