介绍
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,
});
}
dataURL(base64)字符串转 blob 对象
js
function dataURL2blob(dataURI) {
let byteString = atob(dataURI.split(",")[1]);
let mimeString = dataURI.split(",")[0].split(":")[1].split(";")[0];
let ab = new ArrayBuffer(byteString.length);
let ia = new Uint8Array(ab);
for (let i = 0; i < byteString.length; i++) {
ia[i] = byteString.charCodeAt(i);
}
return new Blob([ab], { type: mimeString });
}
读取文件方法 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");
}
应用
直接预览用户选择的图片
- 直接使用
URL.createObjectURL(blob)
获取文件的链接 - 读取图片文件的内容(base64编码)设置给图片元素的
src
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];
// 1. 使用 URL.createObjectURL
// previewImage.src = URL.createObjectURL(file);
// previewImage.style.display = "block";
// 2. 读取文件内容的方式
const { reader } = await readAsDataURL(file); // 使用 reader.js 的方法
previewImage.setAttribute("src", reader.result);
previewImage.style.display = "block";
});
</script>
</body>
</html>
大文件分片上传
这个需要服务端的配合才可以 具体实现, 请查看这个章节
客户端触发文件下载
一般来说, 下载都是用服务端实现来实现的(添加响应头), 但是有些情况下, 需要临时下载一些文本数据, 且数据已经获取到了, 在浏览器端直接触发下载就可以了, 就不用再发送一次请求去触发下载了
服务端实现
js
app.get("/download", function (req, res) {
const filepath = "./public/uploads/1.png";
const filename = path.basename(filepath);
const mimetype = mime.getType(filepath);
// 设置响应头, 返回一个可读流 stream
res.setHeader("Content-disposition", "attachment; filename=" + filename);
res.setHeader("Content-type", mimetype);
fs.createReadStream(filepath).pipe(res);
});
这里的实现没有管低版本浏览器兼容问题, 只是为了笔记记录下载的实现原理, 你可以使用 fileSaver.js 兼容低版本浏览器
js
// 生成文件名
function filenameGenerator(fileExt = "") {
const filename = `${Date.now()}_${Math.random().toString(16).substring(2)}`;
return fileExt ? `${filename}.${fileExt}` : filename;
}
// 触发下载
function triggerDownload(blob, filename) {
const link = document.createElement("a");
const url = URL.createObjectURL(blob);
link.href = url;
link.download = filename;
link.style.display = "none";
document.body.appendChild(link);
link.click(); // mock person click
URL.revokeObjectURL(url);
}
// 下载文本数据
export function downloadText(text, customFilename = "") {
if (typeof text !== "string") {
throw new Error("[downloadText]text must be a string");
}
const filename = customFilename || filenameGenerator(".txt");
const blob = new Blob([text], { type: "text/plain" });
triggerDownload(blob, filename);
}
// 下载 blob 对象
export function downloadBlob(blob, customFilename) {
if (typeof blob !== "object" || !(blob instanceof Blob)) {
throw new Error("[downloadBlob]blob must be a Blob object");
}
const fileExt = blob.type.split("/").pop();
const filename = customFilename || filenameGenerator(fileExt);
triggerDownload(blob, filename);
}
// 应用:
downloadText(`{"id": 1, "name": "test"}`, "some-data.json");
fetch("https://echo.apifox.com/get")
.then((res) => res.blob())
.then((blob) => {
downloadBlob(blob, "some-api-data.json");
});
浏览器端压缩图片再上传
需要借助图片压缩库 browser-image-compression
获取文件的MD5
需要借助加密算法库 crypto-js
扩展
浏览器中允许访问用户系统中文件的 File System API 可用于做Web端的文件编辑器, 因为可以读写用户操作系统的文件, 如: 在线版 vscode