前言
JavaScript语言提供了一些API来处理文件或原始文件数据,例如:File、Blob、FileReader、ArrayBuffer等。如下图,是它们之间的一张关系图:
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)
可见
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 数据对象;
事件
aborterrorloadloadstartloadendprogress: 当上传大文件时,可以通过 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 到 127 | 1 | 8位有符号整数 |
| Uint8Array | 0 到 255 | 1 | 8位无符号整数 |
| Uint8ClampedArray | 0 到 255 | 1 | 8位无符号整数 |
| Int16Array | -32768 到 32767 | 2 | 16位有符号整数 |
| Uint16Array | 0 到 65535 | 2 | 16位无符号整数 |
| Int32Array | -2147483648 到 2147483647 | 4 | 32位有符号整数 |
| Uint32Array | 0 到 4294967295 | 4 | 32位无符号整数 |
| Float32Array | -3.4E38 到 3.4E38 并且 1.2E-38 是最小的正数 | 4 | 32位浮点 |
| Float64Array | -1.8E308 到 1.8E308 并且 5E-324 是最小的正数 | 8 | 64位浮点 |
| BigInt64Array | -263 到 263 - 1 | 8 | 64 位有符号整型(补码) |
| BigUint64Array | 0 到 264 - 1 | 8 | 64 位无符号整型 |
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 对象中。
使用
构造器
使用一个指定的类型化数组创建实例,例如 Int8Array 或 BigInt64Array。这些对象的构造函数是通用的:
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 是一种伪协议,允许将Blob和File对象用作图像、二进制数据下载链接等的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
- 使用 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)
}
})
}
- 使用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
}