【JavaScript API】Blob、File、FileReader、ArrayBuffer、TypeArray

324 阅读10分钟

前言

JavaScript语言提供了一些API来处理文件或原始文件数据,例如:FileBlobFileReaderArrayBuffer等。如下图,是它们之间的一张关系图:

File&Blob.png

Blob和File

看个例子:

const blob = new Blob(['123'], { type: 'text/plain' })
console.log('blob', blob)
const file = new File(['123'], '123.txt')
console.log('file', file)

blob01.png

file01.png

可见File确实是继承自Blob, 从打印出file实例对象看出,file主要是多了文件名和时间戳两个属性。

Blob

Blob 是 Binary Large Object 的简称。
构造方法

const aBlob = new Blob(array, options )

属性和方法

  • type只读属性:返回Blob 对象的 type 属性给出文件的 MIME 类型。如果类型无法确定,则返回空字符串。
  • size只读属性:返回Blob 对象中所包含数据的大小(字节)。
  • arrayBuffer()方法: 返回一个 promise,其会兑现一个包含 Blob 所有内容的二进制格式的 ArrayBuffer。
  • slice()方法:返回一个新的 Blob 对象,包含了源 Blob 对象中指定范围内的数据。
  • stream()方法:返回一个能读取 Blob 内容的 ReadableStream。
  • text()方法: 返回一个 promise,其会兑现一个包含 Blob 所有内容的 UTF-8 格式的字符串。

具体可以参考MDN文档

File

由于File继承自Blob,下面只列出File独有的:

  • lastModified: 返回所引用文件最后修改日期,为自 1970 年 1 月 1 日 0:00 以来的毫秒数。没有已知的最后修改时间则会返回当前时间。
  • name: 返回由 File 对象表示的文件的名称。由于安全原因,该属性并不包含文件路径。

FileReader

FileReader使用 File 或 Blob 对象指定要读取的文件或数据。
下面的例子,读取文件的文本内容

import React from 'react'
export default function MyFuncComp() {
  const handleChange = (evt) => {
    const fileReader = new FileReader()
    fileReader.onload = function(evt) {
      console.log(evt.target.result);
    }
    fileReader.readAsText(evt.target.files[0])
  }
  return (
    <div>
      <input type="file" onChange={handleChange} />
    </div>
  )
}

实例方法

  • readAsText():读取指定 Blob 中的内容,完成之后,result 属性中将包含一个字符串以表示所读取的文件内容。
  • readAsDataURL():读取指定 Blob 中的内容,完成之后,result 属性中将包含一个data: URL 格式的 Base64 字符串以表示所读取文件的内容。
  • readAsArrayBuffer():读取指定 Blob 中的内容,完成之后,result 属性中保存的将是被读取文件的 ArrayBuffer 数据对象;

事件

  • abort
  • error
  • load
  • loadstart
  • loadend
  • progress: 当上传大文件时,可以通过 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)} %`);
  }
});

ArrayBuffer

ArrayBuffer 对象用来表示通用的、固定长度的原始二进制数据缓冲区。
ArrayBuffer 的内容不能直接操作,只能通过 DataView 对象或 TypedArrray 对象来访问,这些对象用于读取和写入缓冲区内容。

  • TypedArray:用来生成内存的视图,通过不同构造函数,可以生成不同数据格式的视图。
  • DataView:用来生成内存的视图,可以自定义格式和字节序。

TypedArray视图和 DataView视图的区别主要是字节序,TypedArray的数组成员都是同一个数据类型,DataView的数组成员可以是不同的数据类型。

TypedArray

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

类型化数组值范围字节描述
Int8Array-128 到 12718位有符号整数
Uint8Array0 到 25518位无符号整数
Uint8ClampedArray0 到 25518位无符号整数
Int16Array-32768 到 32767216位有符号整数
Uint16Array0 到 65535216位无符号整数
Int32Array-2147483648 到 2147483647432位有符号整数
Uint32Array0 到 4294967295432位无符号整数
Float32Array-3.4E38 到 3.4E38 并且 1.2E-38 是最小的正数432位浮点
Float64Array-1.8E308 到 1.8E308 并且 5E-324 是最小的正数864位浮点
BigInt64Array-263 到 263 - 1864 位有符号整型(补码)
BigUint64Array0 到 264 - 1864 位无符号整型
  • Uint8Array: 将 ArrayBuffer 中的每个字节视为一个整数,可能的值从 0 到 255 (一个字节等于 8 位)。 这样的值称为“8 位无符号整数”。
  • Uint16Array:将 ArrayBuffer 中任意两个字节视为一个整数,可能的值从 0 到 65535。 这样的值称为“16 位无符号整数”。
  • Uint32Array:将 ArrayBuffer 中任何四个字节视为一个整数,可能值从 0 到 4294967295,这样的值称为“32 位无符号整数”。

这些构造函数生成的对象统称为 TypedArray 对象。它们和正常的数组很类似,都有length 属性,都能用索引获取数组元素,所有数组的方法都可以在类型化数组上面使用。

每种视图的构造函数都有一个 BYTES_PER_ELEMENT 属性,表示这种数据类型占据的字节数:

Int8Array.BYTES_PER_ELEMENT // 1
Uint8Array.BYTES_PER_ELEMENT // 1
Int16Array.BYTES_PER_ELEMENT // 2
Uint16Array.BYTES_PER_ELEMENT // 2
Int32Array.BYTES_PER_ELEMENT // 4
Uint32Array.BYTES_PER_ELEMENT // 4
Float32Array.BYTES_PER_ELEMENT // 4
Float64Array.BYTES_PER_ELEMENT // 8

类型化数值和数值的区别

  • 类型化数组的元素都是连续的,不会为空;
  • 类型化数组的所有成员的类型和格式相同;
  • 类型化数组元素默认值为 0;
  • 类型化数组本质上只是一个视图层,不会存储数据,数据都存储在更底层的ArrayBuffer 对象中。
使用

构造器 使用一个指定的类型化数组创建实例,例如 Int8ArrayBigInt64Array。这些对象的构造函数是通用的:

new TypedArray(length) // 通过分配指定长度内容进行分配
new TypedArray(object) // 参数可以是一个普通数组
new TypedArray(typedArray) // 接收一个视图实例作为参数

// 这种方式有三个参数,其中第一个参数是一个ArrayBuffer对象;
// 第二个参数是视图开始的字节序号,默认从0开始,可选;
// 第三个参数是视图包含的数据个数,默认直到本段内存区域结束。
new TypedArray(buffer)
new TypedArray(buffer, byteOffset)
new TypedArray(buffer, byteOffset, length)
  • new TypedArray(length)
const view = new Int8Array(16)
view[0] = 2
view[10] = 8
console.log(view)
  • new TypedArray(object)
const view = new Int8Array([1, 2, 3, 4, 5])
view[0] = 8
view[3] = 6
console.log(view)
  • new TypedArray(typeArray)
const view = new Int8Array(new Uint8Array(6))
view[0] = 10
view[3] = 6
console.log(view)

属性

  • buffer: 只读属性,会返回内存中对应的 ArrayBuffer对象
  • byteLength:只读属性,返回 TypedArray 占据的内存长度,单位为字节
  • length:只读属性,返回 TypedArray 元素个数
const view = new Int16Array(8)
console.log(view.length)    // 8
console.log(view.byteLength ) // 16
  • byteOffset:只读属性,表示类型化数组距离其ArrayBuffer起始位置的偏移(字节数)。
const buffer = new ArrayBuffer(8)

const uint8 = new Uint8Array(buffer)
console.log(uint8.byteOffset) // 0 (没有指定 oddfet)

const uint8 = new Uint8Array(buffer, 3)
console.log(uint8.byteOffset) // 3 (在构造 Uint8Array 时指定)

DataView

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

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

参数说明:

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

属性

  • buffer:返回对应的ArrayBuffer对象;
  • byteLength:返回占据的内存字节长度;
  • byteOffset:返回当前视图从对应的ArrayBuffer对象的哪个字节开始。

读取内存

  • getInt8:读取1个字节,返回一个8位整数。
  • getUint8:读取1个字节,返回一个无符号的8位整数。
  • getInt16:读取2个字节,返回一个16位整数。
  • getUint16:读取2个字节,返回一个无符号的16位整数。
  • getInt32:读取4个字节,返回一个32位整数。
  • getUint32:读取4个字节,返回一个无符号的32位整数。
  • getFloat32:读取4个字节,返回一个32位浮点数。
  • getFloat64:读取8个字节,返回一个64位浮点数。

参数说明:

  • byteOffset:偏移量,从头开始计算,单位为字节

例子:

const buffer = new ArrayBuffer(28)
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)

写入内存

  • setInt8:写入1个字节的8位整数。
  • setUint8:写入1个字节的8位无符号整数。
  • setInt16:写入2个字节的16位整数。
  • setUint16:写入2个字节的16位无符号整数。
  • setInt32:写入4个字节的32位整数。
  • setUint32:写入4个字节的32位无符号整数。
  • setFloat32:写入4个字节的32位浮点数。
  • setFloat64:写入8个字节的64位浮点数。

参数说明:

  • byteOffset:偏移量,从头开始计算,单位为字节
  • value:设置的数值

Blob URL / Object URL

Blob URL/Object URL 是一种伪协议,允许将BlobFile对象用作图像、二进制数据下载链接等的URL源。
对于 Blob/File 对象,可以使用URL构造函数的 createObjectURL() 方法创建将给出的对象的 URL。这个 URL 对象表示指定的 File 对象或 Blob 对象。我们可以在<img><script> 标签中或者 <a><link> 标签的 href 属性中使用这个 URL。 例子:

import React, { useRef } from 'react'
export default function MyFuncComp() {
  const imgRef = useRef(null)
  const handleChange = (evt) => {
    imgRef.current.src = URL.createObjectURL(evt.target.files[0])
  }
  return (
    <div>
      <input type="file" placeholder='选择文件' onChange={handleChange} />
      <img ref={imgRef} alt="" />
    </div>
  )
}

内存管理:

在每次调用 createObjectURL() 方法时,都会创建一个新的 URL 对象,即使你已经用相同的对象作为参数创建过。当不再需要这些 URL 对象时,每个对象必须通过调用 URL.revokeObjectURL() 方法来释放。 浏览器在 document 卸载的时候,会自动释放它们,但是为了获得最佳性能和内存使用状况,你应该在安全的时机主动释放掉它们。

Base64

Base64 是一种基于 64 个可打印字符来表示二进制数据的表示方法。 在 JavaScript 中,有两个函数被分别用来处理解码和编码 base64 字符串:

  • atob():解码,解码一个 Base64 字符串;
  • btoa():编码,从一个字符串或者二进制数据编码一个 Base64 字符串。
btoa("hello")       // 'aGVsbG8='
atob('aGVsbG8=') // 'hello'

应用场景

  • canvas画布内容生成base64编码格式的图片
const canvas = document.getElementById('canvas')
const ctx = canvas.getContext("2d")
const dataUrl = canvas.toDataURL()
  • 使用readAsDataURL()方法把上传的文件转为base64格式的data URI
import React, { useState} from 'react'
export default function MyFuncComp() {
  const [imgSrc, setImgSrc] = useState('')
  const reader = new FileReader()
  const handleChange = (evt) => {
    reader.readAsDataURL(evt.target.files[0])
  }
  reader.onload = (e) => {
    setImgSrc(e.target.result)
  }
  return (
    <div>
      <input type="file" placeholder='选择你要上传的文件' onChange={handleChange} />
      <img src={imgSrc} alt="" />
    </div>
  )
}

格式转化

blob → base64

function blobToBase64(blob) {
  return new Promise((resolve) => {
    const reader = new FileReader()
    reader.onloadend = () => resolve(reader.result)
    reader.readAsDataURL(blob)
  })
}

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)
  })
}

base4 → blob

const base64ToBlob = (base64Data, contentType, sliceSize = 512) => {
  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 result = new Blob(byteArrays, {type: contentType})
  return result
}

blob → Object URL

const objectUrl = URL.createObjectURL(blob)

常见转换问题

将图片URL转成Base64

  1. 使用 Image + canvas.toDataURL()
function pathToBase64(path) {
  return new Promise((resolve, reject) => {
    const image = new Image()
    image.setAttribute("crossOrigin",'Anonymous')
    image.src = path
    image.onload = function() {
      const canvas = document.createElement('canvas')
      canvas.height = image.height
      canvas.width = image.width
      const ctx = canvas.getContext('2d')
      ctx.drawImage(image, 0, 0)
      const dataURL = canvas.toDataURL()
      resolve(dataURL)
    }
  })
}
  1. 使用fetch
async function pathToBase64(url) {
  const response = await fetch(url)
  const blob = await response.blob()
  return await blobToBase64(blob) // blobToBase64定义方法见上
}

根据图片URL获取图片大小

async function getImageSize(url) {
  const response = await fetch(url)
  const blob = await response.blob()
  return blob.size
}