最近需要做上传组件,就整体梳理了一下文件上传的相关概念和使用。
唯一文件类型说明符和 MIME 类型
首先,实现上传的是 <input type="file">
。file
类型的 input
有一个属性 accept
,用于限制可接受的文件类型。accept
属性的值,是一个逗号分隔的 唯一文件类型说明符 列表的字符串。
唯一文件类型说明符 的形式有:
- 以英文句号开头的文件名扩展名,如
.png
- MIME 类型 字符串
MIME 类型 是一种用来表示文档、文件等的性质和格式的标准,通用结构是 type/subtype
,常见的例如 text/html
、image/png
、video/*
等( video/*
表示 “任何视频文件”)。
🌰
接受 XLSX 文件和任何图片文件:
<input type="file" accept=".xlsx, image/*">
File
file
类型的 input
还有一个属性是 files
,它的值是一个 fileList
对象,用来存储用户所选择的文件,而每个文件就是一个 File
对象,可以获取有关文件的信息。
File
对象常用的属性有3个:
- name —— 本地文件系统中的文件名
- size —— 文件的字节大小
- type —— 文件的 MIME 类型
🌰
document.querySelector('input').onchange = (e) => {
const fileList = e.target.files;
const file = fileList[0];
console.log(fileList);
console.log('name:', file.name);
console.log('size:', file.size);
console.log('type:', file.type);
}
Blob
Blob
是一个包含只读原始数据的类文件对象。File
类型是 Blob
的子类型,继承了 Blob
的功能并且扩展支持了用户系统上的文件。上述的属性中,size
和 type
就是继承自 Blob
。
接受 Blob
对象的 API 也接受 File
对象,比如 FileReader
、XMLHttpRequest.send()
等,都能处理 Blob
和 File
。
🌰
document.querySelector('input').onchange = (e) => {
const fileList = e.target.files;
const file = fileList[0];
console.log(file instanceof File); // true
console.log(file instanceof Blob); // true
}
🌰
const array = ['<div>hello</div>'];
const options = { type: 'text/html' };
new Blob(array, options);
进度事件
这些事件最早其实只针对 XHR 操作,但目前也被其他 API 借鉴。有以下 6 个进度事件:
- loadstart —— 在接收到响应数据的第一个字节时触发
- progress —— 在接收响应期间,每过 50ms 左右触发一次。
event
属性:- lengthComputable —— 进度信息是否可用。
false
表示总字节数是未知并且total
的值为零 - loaded —— 已经接收的字节数
- total —— 根据
Content-Length
响应头部确定的预期字节数
- lengthComputable —— 进度信息是否可用。
- error —— 在请求发生错误时触发
- abort —— 在因为调用
abort()
方法而终止连接时触发 - load —— 在接收到完整的响应数据时触发
- loadend —— 在触发
error
、abort
或load
事件后触发
FileReader
FileReader
让 Web 应用程序可以异步读取用户系统的文件内容。
常见的方法有:
- readAsText(file[, encoding]) —— 以纯文本形式读取文件,将读取到的文本保存在
result
属性中。第二个参数用于指定编码类型 - readAsDataURL(file) —— 读取文件并将文件以 data: URL 格式的字符串(base64编码)保存在
result
属性中 - readAsArrayBuffer(file) —— 读取文件并将一个包含文件内容的
ArrayBuffer
保存在result
属性中 - abort() —— 中断读取过程
常用的事件为上述 进度事件。其中,触发 error
事件时,可以在 event.target.error
中获取到错误信息 { code: 1 | 2 | 3 | 4 | 5 }
,各数值的含义为:
- 1 —— 未找到文件
- 2 —— 安全性错误
- 3 —— 读取中断
- 4 —— 文件不可读
- 5 —— 编码错误
🌰
获取图片文件的 base64:
document.querySelector('input').onchange = (e) => {
const fileList = e.target.files;
const file = fileList[0];
if (file.type === 'image/png') {
const reader = new FileReader();
reader.readAsDataURL(file);
reader.onload = (event) => {
console.log('base64:', event.target.result);
};
}
}
XMLHttpRequest
XMLHttpRequest
对象用于与服务器交互。
常用的属性有:
- upload —— 返回一个
XMLHttpRequestUpload
对象,用来表示上传的进度 - readyState —— 请求的状态码,值为 4 表示请求已完成
- status —— 数字形式的 HTTP 状态码,值为 2xx 表示用户请求被正确接收、理解和处理
- responseText —— 字符串形式的响应数据
- response —— 整个响应实体
常用的方法有:
- open(method, url[, async]) —— 初始化一个请求
- send(body) —— 发送请求
- abort() —— 中止请求
- setRequestHeader() —— 设置 HTTP 请求头的值。必须在
open()
之后、send()
之前调用
常用的事件为上述 进度事件,再加上一个 readystatechange
,在 readyState
属性变化时触发。
load
事件替代了 readystatechange
事件,也不用检查 readyState
属性了。但需要检查 status
属性来确定数据是否可用。
常在 upload
属性上绑定 onprogress
事件来实现文件上传的进度条。为什么没有直接在 XMLHttpRequest
对象上绑定该事件呢?因为上传和下载都会触发 XMLHttpRequest
对象上的 progress
事件,而只有上传会触发 upload
属性上的 progress
事件。
FormData
如果直接传递文件内容,那么服务器端还需要收集提交的内容,再把它们保存到另一个文件中。所以需要以表单提交的方式来上传文件,即使用 FormData
上传文件,服务器端就会像接收常规表单数据一样正常处理。
FormData
是 XMLHttpRequest 2级 定义的类型,用 append()
方法向其中添加新的键值对。
🌰
文件上传:
const handleUpload = (file) => {
const xhr = new XMLHttpRequest();
const formData = new FormData();
formData.append('file', file);
xhr.upload.onprogress = (e) => {
if (e.lengthComputable) {
console.log('进度:', e.loaded / e.total);
}
};
xhr.onerror = (e) => {
console.log('error:', e);
};
xhr.open('post', 'https://www.mocky.io/v2/5cc8019d300000980a055e76');
xhr.send(formData);
};
document.querySelector('input').onchange = (e) => {
const fileList = e.target.files;
const file = fileList[0];
handleUpload(file);
}
参考
MDN
《JavaScript高级程序设计》