介绍
File 对象
File 对象 和 FileList 对象 多用于文件上传 <input type="file" />
FileList 就是 File 对象的集合, 简单来说 FileList 就是一个数组, 其中每个元素都是一个 File 对象
html
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>JavaScript</title>
</head>
<body>
<div id="app">
<input type="file" id="file-upload" />
</div>
<script>
const fileInput = document.getElementById('file-upload');
fileInput.addEventListener('change', (e) => {
const files = e.target.files; // FileList 对象
const file = files[0]; // File 对象
console.log(file);
});
</script>
</body>
</html>
Blob 对象
Blob 就是 Binary Large OBject
的缩写, 表示一个不可变的, 原始数据类型的文件对象
Blob 可以存储文本或者二进制数据(比如从网络下载的/或者从文件系统读取的)
File 对象继承自 Blob 对象
js
const obj = {
id: 1001,
name: 'test-name',
email: 'test-email@example.com',
token: 'xxx-token-string',
};
const str = JSON.stringify(obj, null, 2);
const blo = new Blob([str], {
type: 'application/json;charset=utf-8',
});
FileReader 对象
- FileReader 可用于异步读取文件(File/Blob)内容
- FileReaderSync 可用于同步阻塞读取文件(File/Blob)内容
js
const obj = {
id: 1001,
name: 'test-name',
email: 'test-email@example.com',
token: 'xxx-token-string',
};
const str = JSON.stringify(obj, null, 2);
const blo = new Blob([str], {
type: 'application/json;charset=utf-8',
});
// 读取 blob 对象内容
// 可以读取 blob 对象, 就可以读取 file 对象
// 因为两个对象可以互相转换
const reader = new FileReader();
reader.readAsText(blo);
reader.onload = (e) => {
console.log(e.target.result);
};
对象转换
file 对象转 blob 对象
js
function file2blob(file) {
if (!(file instanceof File)) {
throw new TypeError('[file2blob]:paramater is not instanceof File');
}
return new File([file], file.name, {
type: file.type,
});
}
blob 对象装 file 对象
js
function blob2file(blob, filetype = 'text/plain', filename) {
if (!(blob instanceof Blob)) {
throw new TypeError('[blob2file]paramater is not instanceof Blob');
}
const fname = filename || Math.random().toString(16).substring(2);
return new File([blob], fname, {
type: filetype,
});
}
读取文件方法 Promise 封装
js
function isCallable(fn) {
return typeof fn === 'function';
}
function read(file, userHandlers = {}, readAsFunc = null) {
const allowAsFuncs = ['readAsText', 'readAsArrayBuffer', 'readAsDataURL', 'readAsBinaryString'];
if (!allowAsFuncs.includes(readAsFunc)) {
throw new Error(`${readAsFunc} is not supported in FileReader API`);
}
const handlers = Object.assign(
{
onAbort: null,
onError: null,
onLoad: null,
onLoadEnd: null,
onLoadStart: null,
onProgress: null,
},
userHandlers,
);
return new Promise((resolve, reject) => {
const reader = new FileReader();
// reject
reader.addEventListener('abort', (event) => {
isCallable(handlers.onAbort) && handlers.onAbort();
reject({ reject_type: 'abort', event, reader, file });
});
reader.addEventListener('error', (event) => {
isCallable(handlers.onError) && handlers.onError();
reject({ reject_type: 'error', event, reader, file });
});
// resolve
reader.addEventListener('load', (event) => {
resolve({ event, reader, file });
});
// other callbacks
reader.addEventListener('loadend', (event) => {
isCallable(handlers.onLoadEnd) && handlers.onLoadEnd(event);
});
reader.addEventListener('loadstart', (event) => {
isCallable(handlers.onLoadStart) && handlers.onLoadStart(event);
});
reader.addEventListener('progress', (event) => {
isCallable(handlers.onProgress) && handlers.onProgress(event);
});
reader[readAsFunc].call(reader, file);
});
}
export function readAsText(file, handlers) {
return read(file, handlers, 'readAsText');
}
export function readAsBinaryString(file, handlers) {
return read(file, handlers, 'readAsBinaryString');
}
export function readAsDataURL(file, handlers) {
return read(file, handlers, 'readAsDataURL');
}
export function readAsArrayBuffer(file, handlers) {
return read(file, handlers, 'readAsArrayBuffer');
}
应用
直接预览用户选择的图片
html
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>JavaScript</title>
</head>
<body>
<div id="app">
<input type="file" id="file-upload" />
<img src="" id="preview" style="display: none" />
</div>
<script src="./reader.js"></script>
<script>
const fileInput = document.getElementById('file-upload');
const previewImage = document.getElementById('preview');
fileInput.addEventListener('change', async (e) => {
const file = e.target.files[0];
const { reader } = await readAsDataURL(file); // 使用 reader.js 的方法
previewImage.setAttribute('src', reader.result);
previewImage.style.display = 'block';
});
</script>
</body>
</html>
大文件分片上传
- 需要将 file 文件转 blob 对象
- 使用 blob 的 slice 方法切片
- 利用 FormData 对象组合数据
- 发送请求携带(FormData)数据
js
const fileInput = document.getElementById('file-upload');
fileInput.addEventListener('change', async (e) => {
const file = e.target.files[0];
const iter = slice(file);
const originFileInfo = {
name: file.name,
size: file.size,
type: file.type,
};
// 切分一次, 就上传一次
let item = iter.next();
let chunkIndex = 0;
while (!item.done) {
console.log('此次上传数据:', chunkIndex, item.value);
const formData = buildFormData(originFileInfo, chunkIndex, iter.value);
await request(formData);
chunkIndex++;
item = iter.next();
}
});
// 大文件切分
function* slice(blob, chunkSizeMB = 1, sliceType = '') {
const totalSize = blob.size;
const chunkSize = Math.pow(1024, 2) * chunkSizeMB;
let start = 0;
let end = 0;
while (start < totalSize) {
end = start + chunkSize;
yield blob.slice(start, end, sliceType);
start = end;
}
}
// 构建提交表单
function buildFormData(originFileInfo, index, blob) {
const formData = new FormData();
formData.append('origin_file_info', JSON.stringify(originFileInfo));
formData.append('slice_index', index);
formData.append('data', blob);
return formData;
}
// 发送请求(模拟)
function request(formData) {
return new Promise((resolve) => {
setTimeout(() => {
console.log('上传成功', formData);
resolve();
}, 500);
});
}
客户端触发文件下载
js
const fileInput = document.getElementById('file-upload');
fileInput.addEventListener('change', async (e) => {
const file = e.target.files[0];
const filename = Math.random().toString(32) + file.name;
download(file, filename);
});
function download(file, filename) {
const url = URL.createObjectURL(file);
const link = document.createElement('a');
link.href = url;
link.download = filename;
link.click();
URL.revokeObjectURL(url);
}
浏览器端压缩图片再上传
需要借助图片压缩库 browser-image-compression
获取文件的MD5
需要借助加密算法库 crypto-js
扩展
浏览器中允许访问用户系统中文件的 File System API 可用于做Web端的文件编辑器, 因为可以读写用户操作系统的文件, 如: 在线版 vscode