文件操作相关

文件操作相关

1. Blob、File、Base64 概述

1.1 Blob 对象

Blob(Binary Large Object) 是浏览器提供的一个原生对象,用于表示不可变的、原始数据的类文件对象。

二进制数据本质:

在计算机中,所有数据最终都以二进制形式存储和传输:

  • 文本文件:每个字符对应一个或多个字节的二进制编码
  • 图片文件:像素数据以二进制格式存储
  • 音频/视频:采样数据以二进制格式存储
  • 程序文件:机器码以二进制格式存储

Blob 的作用:

Blob 对象是浏览器中表示原始二进制数据的标准方式,它:

  • 封装二进制数据:将底层的字节序列包装成高级对象
  • 提供统一接口:无论数据来源如何,都提供相同的操作方式
  • 支持流式处理:可以分片读取和处理大文件

特点:

  • 不可变性:一旦创建,内容无法修改
  • 原始数据:直接存储二进制字节,不进行任何编码转换
  • 类文件对象:具有类似文件的结构,但没有文件名等元信息
  • 内存效率:直接操作二进制数据,避免不必要的编码转换

创建方式:

1
2
3
4
5
6
7
8
// 1. 从字符串创建
const blob1 = new Blob(['Hello World'], { type: 'text/plain' })

// 2. 从数组创建
const blob2 = new Blob([new Uint8Array([1, 2, 3])], { type: 'application/octet-stream' })

// 3. 从其他 Blob 创建
const blob3 = new Blob([blob1, blob2])

常用属性和方法:

1
2
3
4
5
6
const blob = new Blob(['Hello'], { type: 'text/plain' })
console.log(blob.size) // 5 (字节数)
console.log(blob.type) // 'text/plain'

// 切片方法
const slice = blob.slice(0, 3) // 获取前3个字节

1.2 File 对象

File 对象 继承自 Blob 对象,专门用于表示文件信息,包含了文件的元数据。

与 Blob 的关系:

File 对象本质上是带有元信息的 Blob

  • 数据部分:继承自 Blob,存储文件的二进制内容
  • 元信息部分:添加文件名、类型、修改时间等文件属性
  • 完全兼容:File 对象可以直接当作 Blob 使用

二进制数据流:

1
用户选择文件 → 浏览器读取文件系统 → 创建 File 对象 → 包含二进制数据 + 元信息

特点:

  • 继承自 Blob:拥有 Blob 的所有特性和方法
  • 包含元信息:文件名、最后修改时间、文件类型等
  • 用户选择:通常通过 <input type="file"> 获取
  • 二进制存储:文件内容以原始二进制形式存储

获取方式:

1
2
3
4
5
6
7
8
9
// 通过文件输入框获取
const fileInput = document.querySelector('input[type="file"]')
const file = fileInput.files[0]

// 通过拖拽获取
const dropZone = document.querySelector('.drop-zone')
dropZone.addEventListener('drop', (e) => {
const file = e.dataTransfer.files[0]
})

常用属性:

1
2
3
4
5
6
const file = fileInput.files[0]
console.log(file.name) // 文件名
console.log(file.size) // 文件大小(字节)
console.log(file.type) // MIME 类型
console.log(file.lastModified) // 最后修改时间
console.log(file.webkitRelativePath) // 相对路径(文件夹上传时)

1.3 Base64 编码

Base64 是一种用64个可打印字符来表示二进制数据的编码方法。

二进制到文本的转换:

Base64 的核心作用是将二进制数据转换为文本格式

  • 输入:任意二进制数据(字节序列)
  • 输出:可打印的 ASCII 字符字符串
  • 目的:在文本协议中安全传输二进制数据

编码原理:

1
二进制数据 → 每3个字节(24位) → 分成4组(每组6位) → 映射到64个字符 → Base64字符串

为什么需要 Base64:

  1. 文本协议兼容:HTTP、SMTP 等协议只能传输文本
  2. 数据完整性:避免二进制数据在传输过程中被误解释
  3. 跨平台兼容:不同系统对二进制数据的处理方式可能不同
  4. 存储安全:某些存储系统不支持二进制数据

特点:

  • 文本格式:可以用文本方式传输二进制数据
  • 大小增加:编码后大小约为原数据的 4/3 倍(每3字节变成4字符)
  • URL 安全:某些字符在 URL 中需要转义
  • 可逆性:可以完全还原为原始二进制数据

字符集:

1
2
3
4
5
A-Z (26个字符)
a-z (26个字符)
0-9 (10个字符)
+ / (2个字符)
= (填充字符)

使用场景:

1
2
3
4
5
6
7
8
9
10
// 1. Data URL 格式
const dataUrl = 'data:text/plain;base64,SGVsbG8gV29ybGQ='

// 2. 纯 Base64 字符串
const base64String = 'SGVsbG8gV29ybGQ='

// 3. 在 JSON 中传输
const jsonData = {
image: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAA...'
}

1.4 三者关系与二进制数据流

数据流转图:

1
2
3
文件系统(二进制) → File对象(二进制+元信息) → Blob对象(纯二进制) → Base64(文本编码)
↑ ↓ ↓ ↓
原始数据 用户操作接口 内存操作接口 传输/存储接口

二进制数据关联:

  1. 共同基础:三者都基于相同的二进制数据
  2. 不同表示:同一份数据的不同表现形式
  3. 转换无损:转换过程中二进制内容保持不变
  4. 用途不同:针对不同的使用场景优化

关系说明:

  1. File 是 Blob 的子类:File = Blob + 文件元信息
  2. Blob ↔ Base64:二进制数据 ↔ 文本编码
  3. File ↔ Base64:通过 Blob 作为中间桥梁
  4. 数据一致性:转换过程中二进制内容完全一致

2. 文件上传

2.1 基本文件上传

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// 上传文件接口
export function upFile(data) {
return request({
url: '/ruo/file/upload',
method: 'post',
data
})
}

// 文件上传实现
const uploadFile = (file) => {
// 创建一个formData对象
const formData = new FormData()
formData.append('file', file)

// 上传接口
upFile(formData).then((res) => {
// 上传文件ID 和其他数据一起跟随新增接口传给后台
form.fileId = res.fileId
saveUp(form).then(() => {
this.$refs.pdmEdit.hide()
this.$message.success('修改成功!')
})
})
}

2.2 文件上传进度监控

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const uploadWithProgress = (file, onProgress) => {
const formData = new FormData()
formData.append('file', file)

return request({
url: '/upload',
method: 'post',
data: formData,
onUploadProgress: (progressEvent) => {
const percentCompleted = Math.round((progressEvent.loaded * 100) / progressEvent.total)
onProgress(percentCompleted)
}
})
}

3. 文件下载

3.1 Blob 方式下载

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
// 下载文件接口
export function downloadPdm(data) {
return request({
url: '/ruo/file/downloadFile',
method: 'get',
params: data,
responseType: 'blob'
})
}

// 写法一:创建临时链接下载
downloadPdm({ id: data.id }).then(res => {
const fileName = decodeURIComponent(res.headers['content-disposition'].split('=')[1])
const link = document.createElement('a')
link.download = fileName
// 创建URL对象
link.href = URL.createObjectURL(new Blob([res.data]))
document.body.appendChild(link)
link.click()
// 释放一个通过URL.createObjectURL()创建的对象URL
URL.revokeObjectURL(link.href)
document.body.removeChild(link)
})

// 写法二:新窗口打开下载
downloadPdm({ id: data.id }).then(res => {
const fileName = decodeURIComponent(res.headers['content-disposition'].split('=')[1])
const link = document.createElement('a')
link.href = window.URL.createObjectURL(new Blob([res.data]))
link.target = '_blank'
// 文件名和格式
link.setAttribute('download', fileName + '.apk')
link.click()
document.body.removeChild(link)
})

3.2 通用文件下载函数

1
2
3
4
5
6
7
8
9
10
const downloadFile = (blob, fileName) => {
const url = window.URL.createObjectURL(blob)
const link = document.createElement('a')
link.href = url
link.download = fileName
document.body.appendChild(link)
link.click()
document.body.removeChild(link)
window.URL.revokeObjectURL(url)
}

4. File、Blob、Base64 相互转换

4.1 File 转 Base64

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const fileToBase64 = (file) => {
return new Promise((resolve, reject) => {
const reader = new FileReader()
reader.onload = () => resolve(reader.result)
reader.onerror = reject
reader.readAsDataURL(file)
})
}

// 使用示例
const fileInput = document.querySelector('input[type=file]')
const file = fileInput.files[0]
fileToBase64(file).then(base64 => {
console.log(base64) // data:text/plain;base64,SGVsbG8sIHdvcmxkIQ==
})

4.2 Base64 转 Blob

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
const base64ToBlob = (base64String, mimeType = '') => {
// 去除 Data URL 前缀
const base64Content = base64String.split(',')[1]
const byteCharacters = atob(base64Content)
const byteNumbers = new Array(byteCharacters.length)

for (let i = 0; i < byteCharacters.length; i++) {
byteNumbers[i] = byteCharacters.charCodeAt(i)
}

const byteArray = new Uint8Array(byteNumbers)
return new Blob([byteArray], { type: mimeType })
}

// 使用示例
const base64String = 'data:text/plain;base64,SGVsbG8sIHdvcmxkIQ=='
const blob = base64ToBlob(base64String, 'text/plain')

4.3 Blob 转 Base64

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const blobToBase64 = (blob) => {
return new Promise((resolve, reject) => {
const reader = new FileReader()
reader.onload = () => resolve(reader.result)
reader.onerror = reject
reader.readAsDataURL(blob)
})
}

// 使用示例
const blob = new Blob(['Hello, world!'], { type: 'text/plain' })
blobToBase64(blob).then(base64 => {
console.log(base64)
})

4.4 Base64 转 File

1
2
3
4
5
6
7
8
const base64ToFile = (base64String, fileName, mimeType = '') => {
const blob = base64ToBlob(base64String, mimeType)
return new File([blob], fileName, { type: mimeType })
}

// 使用示例
const base64String = 'data:text/plain;base64,SGVsbG8sIHdvcmxkIQ=='
const file = base64ToFile(base64String, 'example.txt', 'text/plain')

4.5 File 转 Blob

1
2
3
4
5
6
// File 本身就是 Blob 的子类,可以直接使用
const file = document.querySelector('input[type=file]').files[0]
const blob = file // File 可以直接当作 Blob 使用

// 或者创建新的 Blob
const newBlob = new Blob([file], { type: file.type })

5. 实际应用场景

5.1 图片预览

1
2
3
4
5
6
7
8
9
const previewImage = (file) => {
const reader = new FileReader()
reader.onload = (e) => {
const img = document.createElement('img')
img.src = e.target.result
document.body.appendChild(img)
}
reader.readAsDataURL(file)
}

5.2 文件压缩

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
const compressImage = (file, quality = 0.8) => {
return new Promise((resolve) => {
const canvas = document.createElement('canvas')
const ctx = canvas.getContext('2d')
const img = new Image()

img.onload = () => {
canvas.width = img.width
canvas.height = img.height
ctx.drawImage(img, 0, 0)

canvas.toBlob(resolve, 'image/jpeg', quality)
}

img.src = URL.createObjectURL(file)
})
}

5.3 大文件分片上传

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
const uploadLargeFile = async (file, chunkSize = 2 * 1024 * 1024) => {
const chunks = []
let start = 0

while (start < file.size) {
const end = Math.min(start + chunkSize, file.size)
const chunk = file.slice(start, end)
chunks.push(chunk)
start = end
}

for (let i = 0; i < chunks.length; i++) {
const formData = new FormData()
formData.append('chunk', chunks[i])
formData.append('index', i)
formData.append('total', chunks.length)
formData.append('filename', file.name)

await uploadChunk(formData)
}
}

6. 注意事项

  1. 内存管理:使用 URL.createObjectURL() 后记得调用 URL.revokeObjectURL() 释放内存
  2. 文件大小限制:Base64 编码会增加约33%的文件大小
  3. 浏览器兼容性:某些老版本浏览器可能不支持某些 API
  4. 安全性:上传文件时要注意文件类型验证和大小限制

7. 总结

File、Blob、Base64 是前端文件操作的核心概念:

  • File:包含文件信息的对象,继承自 Blob
  • Blob:二进制数据的容器,用于文件下载和传输
  • Base64:文本格式的二进制数据表示,便于传输和存储