一、file对象基本属性
1、获取文件对象
javascript
// 通过文件输入框获取
const fileInput = document.getElementById('fileInput');
const file = fileInput.files[0];
// 通过拖拽获取
dropZone.addEventListener('drop', (e) => {
e.preventDefault();
const file = e.dataTransfer.files[0];
});
2、file对象的一些属性
javascript
if (file) {
console.log('文件名:', file.name); // "example.jpg"
console.log('文件大小:', file.size); // 字节数
console.log('文件类型:', file.type); // "image/jpeg"
console.log('最后修改时间:', file.lastModified); // 时间戳
console.log('文件对象:', file); // File 对象本身
}
二、文件读取方法
1、FileReader API
浏览器提供的用于异步读取用户计算机文件内容,主要用于通过<input type="file">或拖放操作选择的文件
(1)核心功能:
- 异步读取文件(不阻塞主线程);
- 有文本,dataurl,arraybuffer,二进制字符串四种读取格式;
- 通过事件监听处理读取结果;
- 只能读取用户明确选择的文件。
(2)使用方法以及读取方式
javascript
// 创建 FileReader 实例
const reader = new FileReader();
// 事件监听
reader.onload = (event) => {
console.log('读取完成:', event.target.result);
};
reader.onerror = (event) => {
console.error('读取失败:', event.target.error);
};
reader.onprogress = (event) => {
if (event.lengthComputable) {
const percent = (event.loaded / event.total) * 100;
console.log(`读取进度: ${percent}%`);
}
};
三种读取方法
javascript
//readAsText() -----读取为文本
reader.readAsText(file,encoding="UTF-8");
reader.onloand=function(){
//处理文本内容
const text=reader.result;
console.log("文本内容:",text);
};
//readAsDataURL() -----读取为DataURL
reader.readAsDataURL(file) //适合图片预览
reader.onload=function(){
//直接用作图片src
const img=document.createElement('img');
img.src=reader.result; //data:image/png;base64,iVBOR...
document.body.appendChild(img);
};
//readAsArrayBuffer() -------读取为ArrayBuffer
reader.readAsArrayBuffer(file);//处理二进制数据
reader.onload=function(){
const buffer=reader.result;
//处理二进制数据
const view=new DataView(buffer);
console.log('第一个字节:',view.getUint8(0));
//转换为其他格式
const blob=new Blob([buffer]);
const text=new TextDecoder().decode(buffer);
};
filereader使用事件驱动模型

javascript
const reader = new FileReader();
const file =someFile;
//1、开始读取时触发
reader.onloadstart=function(event){
console.log("开始读取文件...");
document.getElementById('progress').textContent="读取中..."
};
//2、读取过程中触发(用于跟踪进度)
reader.onprogress=function (event){
if(event.lengthComputable){
const percent=(event.loaded/event.total)*100;
console.log(`读取进度:${percent.toFixed(2)}`);
document.getElementById('progress').textContent=`${percent.toFixed(2)}`;
}
};
//3、读取完成时触发
reader.onload=function(event){
console.log('读取完成!');
const result=event.target.result;
};
//4、读取失败触发
reader.onerror=function (event){
console.log('读取错误',reader.error);
document.getElementById('error').textContent=`${reader.error.name}`;
};
//5、读取结束(无论成功与失败都会触发)
reader.onloadend=function(event){
console.log("读取结束");
document.getElementById('progress').textContent="完成";
};
//6、读取中止时触发
reader.onabort = (event) => {
console.log('读取被中止');
};
reader.readAsText(file);
三、文件验证方法
1、基本验证
javascript
function validateFile(file) {
// 检查是否选择了文件
if (!file) {
return { valid: false, error: '请选择文件' };
}
// 检查文件大小(例如限制为5MB)
const maxSize = 5 * 1024 * 1024; // 5MB
if (file.size > maxSize) {
return {
valid: false,
error: `文件太大,不能超过 ${maxSize / 1024 / 1024}MB`
};
}
// 检查文件类型
const allowedTypes = [
'image/jpeg',
'image/png',
'image/gif',
'application/pdf',
'text/plain'
];
if (!allowedTypes.includes(file.type)) {
return { valid: false, error: '不支持的文件类型' };
}
// 检查文件名长度
if (file.name.length > 100) {
return { valid: false, error: '文件名过长' };
}
return { valid: true, error: null };
}
2、图片文件专门验证
javascript
function validateImage(file) {
return new Promise((resolve, reject) => {
if (!file.type.startsWith('image/')) {
reject('不是图片文件');
return;
}
const img = new Image();
const reader = new FileReader();
reader.onload = (e) => {
img.src = e.target.result;
img.onload = () => {
// 检查图片尺寸
const maxWidth = 1920;
const maxHeight = 1080;
if (img.width > maxWidth || img.height > maxHeight) {
reject(`图片尺寸过大,最大支持 ${maxWidth}x${maxHeight}`);
} else {
resolve({
width: img.width,
height: img.height,
aspectRatio: img.width / img.height
});
}
};
img.onerror = () => {
reject('图片加载失败,可能已损坏');
};
};
reader.readAsDataURL(file);
});
}
四、文件处理方法
1、切片上传(文件过大)
javascript
function uploadFileInChunks(file, chunkSize = 1024 * 1024) { // 1MB 分片
const totalChunks = Math.ceil(file.size / chunkSize);
const fileId = Date.now() + '-' + Math.random().toString(36).substr(2);
for (let chunkIndex = 0; chunkIndex < totalChunks; chunkIndex++) {
const start = chunkIndex * chunkSize;
const end = Math.min(start + chunkSize, file.size);
const chunk = file.slice(start, end);
const formData = new FormData();
formData.append('fileId', fileId);
formData.append('chunkIndex', chunkIndex);
formData.append('totalChunks', totalChunks);
formData.append('chunk', chunk);
formData.append('fileName', file.name);
uploadChunk(formData, chunkIndex + 1, totalChunks);
}
}
async function uploadChunk(formData, current, total) {
try {
const response = await fetch('/api/upload-chunk', {
method: 'POST',
body: formData
});
console.log(`上传分片 ${current}/${total}`);
updateProgress(current, total);
return response.json();
} catch (error) {
console.error('分片上传失败:', error);
throw error;
}
}
2、压缩处理
javascript
// 图片压缩
function compressImage(file, quality = 0.8, maxWidth = 800) {
return new Promise((resolve, reject) => {
const reader = new FileReader();
const img = new Image();
reader.onload = (e) => {
img.src = e.target.result;
img.onload = () => {
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
// 计算新尺寸
let width = img.width;
let height = img.height;
if (width > maxWidth) {
height = (height * maxWidth) / width;
width = maxWidth;
}
canvas.width = width;
canvas.height = height;
// 绘制并压缩
ctx.drawImage(img, 0, 0, width, height);
canvas.toBlob(
(blob) => {
const compressedFile = new File(
[blob],
file.name,
{ type: file.type }
);
resolve(compressedFile);
},
file.type,
quality
);
};
img.onerror = () => reject('图片加载失败');
};
reader.onerror = () => reject('文件读取失败');
reader.readAsDataURL(file);
});
}
五、文件的核心API
1、Blob对象
是JavaScript表示原始二进制数据的对象,它可以存储文本,图像,音频,视频等各类型的数据。
blob的特点:
- 是一个二进制容器可以存储任何类型的数据(就像是一个数据快递箱可以将任何原始数据打包成一个整体,并且为该快递箱打上标签);
- 创建后内容不能修改(但可切片创建新的);
- 创建时通常只是创建对原始数据的引用,不会立即复制数据,只有实际读取时才处理;
- 可用于文件处理,数据存储,网络传输等。
(1)基本创建方式
javascript
// 方法1:使用 Blob 构造函数
const blob1 = new Blob(['文本内容'], {
type: 'text/plain' // MIME 类型
});
// 方法2:从字符串创建
const text = '这是一段文本';
const blob2 = new Blob([text], { type: 'text/plain;charset=utf-8' });
// 方法3:从数组缓冲区创建
const buffer = new ArrayBuffer(8);
const view = new Int32Array(buffer);
view[0] = 42;
view[1] = 100;
const blob3 = new Blob([buffer], { type: 'application/octet-stream' });
// 方法4:混合不同类型的数据
const blob4 = new Blob([
'文本部分\n',
new Uint8Array([65, 66, 67]), // ABC
'\n结束'
], { type: 'text/plain' });
(2)Blob的核心属性
javascript
const blob = new Blob(['Hello, World!'], {
type: 'text/plain',
endings: 'transparent' // 可选:native, transparent
});
console.log('=== Blob 属性 ===');
console.log('大小(字节):', blob.size); // 13
console.log('MIME 类型:', blob.type); // "text/plain"
console.log('是否已关闭:', blob.isClosed); // false(非标准)
console.log('对象本身:', blob); // Blob {size: 13, type: "text/plain"}
(3)核心方法
对于Blob能使用分片功能说明它是由头部以及数据部分组成的(由应用层实现),blob的分片头部:

并且在每个分片上传后都有HTTP响应;所有分片上传后,服务器验证并确认。
javascript
//slice() 切片
const blob = new Blob(['0123456789ABCDEF'], { type: 'text/plain' });
// 切片使用方式
const slice1 = blob.slice(); // 整个 Blob 的副本
const slice2 = blob.slice(0, 5); // 前5字节
const slice3 = blob.slice(5, 10); // 5-10字节
const slice4 = blob.slice(-5); // 最后5字节
const slice5 = blob.slice(0, -5); // 除了最后5字节
const slice6 = blob.slice(5, 10, 'text/plain'); // 指定新类型
console.log('切片示例:');
console.log('原始大小:', blob.size); // 16
console.log('slice2 大小:', slice2.size); // 5
console.log('slice2 类型:', slice2.type); // "text/plain"
//stream() 获取流
async function processBlobStream(blob) {
// 创建可读流
const stream = blob.stream();
const reader = stream.getReader();
let totalBytes = 0;
while (true) {
const { done, value } = await reader.read();
if (done) break;
totalBytes += value.length;
console.log(`读取了 ${value.length} 字节,累计 ${totalBytes}`);
}
reader.releaseLock();
console.log('流处理完成');
}
// 使用
const blob = new Blob(['a'.repeat(1000)]);
processBlobStream(blob);
//arrayBufer() 转换为 ArrayBuffer
async function blobToArrayBuffer(blob) {
const arrayBuffer = await blob.arrayBuffer();
console.log('ArrayBuffer 大小:', arrayBuffer.byteLength);
// 可以进一步处理
const uint8Array = new Uint8Array(arrayBuffer);
const dataView = new DataView(arrayBuffer);
return arrayBuffer;
}
//text() 读取为文本
async function blobToText(blob) {
const text = await blob.text();
console.log('文本内容:', text);
return text;
}
// 处理不同编码
async function blobToTextWithEncoding(blob, encoding = 'utf-8') {
const arrayBuffer = await blob.arrayBuffer();
const decoder = new TextDecoder(encoding);
return decoder.decode(arrayBuffer);
}
(4)Blob URL与Data URL
- data url:内容直接包含在url中,自然包含,不需要额外文件;

- blob url:只是一个引用/指针

两者区别:

javascript
// 创建 Blob URL
function createBlobURL(blob) {
const blobURL = URL.createObjectURL(blob);
console.log('创建的 Blob URL:', blobURL);
// 格式:blob:http://example.com/uuid
return blobURL;
}
// 使用示例
const textBlob = new Blob(['Hello, World!'], { type: 'text/plain' });
const blobURL = createBlobURL(textBlob);
// 可以像普通 URL 一样使用
const iframe = document.createElement('iframe');
iframe.src = blobURL;
document.body.appendChild(iframe);
// 或者用于图片
const imageBlob = someImageBlob;
const imageURL = URL.createObjectURL(imageBlob);
const img = new Image();
img.src = imageURL;
document.body.appendChild(img);
// 使用完毕后一定要释放!
setTimeout(() => {
URL.revokeObjectURL(blobURL);
URL.revokeObjectURL(imageURL);
console.log('URL 已释放');
}, 1000);
//data url
// Blob 转 Data URL
async function blobToDataURL(blob) {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = (e) => resolve(e.target.result);
reader.onerror = reject;
reader.readAsDataURL(blob);
});
}
// Data URL 转 Blob
function dataURLToBlob(dataURL) {
const arr = dataURL.split(',');
const mime = arr[0].match(/:(.*?);/)[1];
const bstr = atob(arr[1]);
let n = bstr.length;
const u8arr = new Uint8Array(n);
while (n--) {
u8arr[n] = bstr.charCodeAt(n);
}
return new Blob([u8arr], { type: mime });
}
// 比较两种 URL
async function compareURLs(blob) {
const blobURL = URL.createObjectURL(blob);
const dataURL = await blobToDataURL(blob);
console.log('=== URL 比较 ===');
console.log('Blob URL 长度:', blobURL.length); // 较短
console.log('Data URL 长度:', dataURL.length); // 较长(base64编码)
console.log('Blob URL 前缀:', blobURL.substring(0, 20));
console.log('Data URL 前缀:', dataURL.substring(0, 50));
// 释放资源
URL.revokeObjectURL(blobURL);
return { blobURL, dataURL };
}
(5)转换矩阵
javascript
class BlobConverter {
// Blob → ArrayBuffer
static async toArrayBuffer(blob) {
return await blob.arrayBuffer();
}
// Blob → String
static async toString(blob, encoding = 'utf-8') {
return await blob.text();
}
// Blob → Base64
static async toBase64(blob) {
const dataURL = await this.toDataURL(blob);
return dataURL.split(',')[1]; // 去掉前缀
}
// Blob → Data URL
static async toDataURL(blob) {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = () => resolve(reader.result);
reader.onerror = reject;
reader.readAsDataURL(blob);
});
}
// Blob → Object URL
static toObjectURL(blob) {
return URL.createObjectURL(blob);
}
// String → Blob
static fromString(str, type = 'text/plain') {
return new Blob([str], { type });
}
// Base64 → Blob
static fromBase64(base64, type = '') {
const byteString = atob(base64);
const arrayBuffer = new ArrayBuffer(byteString.length);
const uint8Array = new Uint8Array(arrayBuffer);
for (let i = 0; i < byteString.length; i++) {
uint8Array[i] = byteString.charCodeAt(i);
}
return new Blob([arrayBuffer], { type });
}
// ArrayBuffer → Blob
static fromArrayBuffer(buffer, type = 'application/octet-stream') {
return new Blob([buffer], { type });
}
// JSON → Blob
static fromJSON(data, type = 'application/json') {
return new Blob([JSON.stringify(data)], { type });
}
// FormData → Blob(模拟表单提交)
static fromFormData(formData) {
return new Blob([new URLSearchParams(formData).toString()], {
type: 'application/x-www-form-urlencoded'
});
}
}
2、File API
file对象是特殊的blob对象,表示用户选择的文件。

(1)file对象结构
javascript
// File 对象结构
const file = {
name: "example.jpg", // 文件名
size: 102400, // 文件大小(字节)
type: "image/jpeg", // MIME 类型
lastModified: 1672502400000, // 最后修改时间戳
lastModifiedDate: Date, // 最后修改日期对象
webkitRelativePath: "" // 相对路径(webkit浏览器)
};
(2)获取file对象的方式
文本输入框方式
<input type="file" id="fileInput">
javascript
// 获取单个文件
const fileInput = document.getElementById('fileInput');
const singleFile = fileInput.files[0];
// 获取多个文件
//<input type="file" id="multiFileInput" multiple>
const multiFileInput = document.getElementById('multiFileInput');
const fileList = multiFileInput.files; // FileList 对象
const filesArray = Array.from(fileList); // 转为数组
// 监听文件变化
fileInput.addEventListener('change', (e) => {
const file = e.target.files[0];
console.log('选择的文件:', file);
});
拖拽获取
javascript
const dropZone = document.getElementById('dropZone');
dropZone.addEventListener('drop', (e) => {
e.preventDefault();
// 获取拖放的文件
const files = e.dataTransfer.files;
if (files.length > 0) {
const file = files[0];
console.log('拖放的文件:', file);
}
});
// 阻止默认拖放行为
['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => {
dropZone.addEventListener(eventName, (e) => {
e.preventDefault();
e.stopPropagation();
});
});
文件选择器api
javascript
async function selectFile() {
try {
const [fileHandle] = await window.showOpenFilePicker({
types: [
{
description: 'Images',
accept: { 'image/*': ['.png', '.jpg', '.jpeg', '.gif'] }
}
],
multiple: false
});
const file = await fileHandle.getFile();
return file;
} catch (err) {
if (err.name !== 'AbortError') {
console.error('选择文件失败:', err);
}
}
}
(3)核心属性
javascript
function inspectFile(file) {
console.log('=== 文件信息 ===');
console.log('文件名:', file.name); // "example.jpg"
console.log('文件扩展名:', file.name.split('.').pop()); // "jpg"
console.log('文件大小:', file.size, '字节'); // 102400
console.log('格式化大小:', formatFileSize(file.size)); // "100 KB"
console.log('MIME类型:', file.type); // "image/jpeg"
console.log('最后修改时间戳:', file.lastModified); // 1672502400000
console.log('最后修改日期:', new Date(file.lastModified));
// 浏览器特定属性
console.log('相对路径:', file.webkitRelativePath || 'N/A');
console.log('是目录?', file instanceof File ? '是' : '否');
}
// 格式化文件大小
function formatFileSize(bytes) {
if (bytes === 0) return '0 Bytes';
const k = 1024;
const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
}
(4)继承自Blob的方法
javascript
// File 继承自 Blob,所以可以使用 Blob 的所有方法
const file = someFile;
// 1. slice() - 切片文件
const chunk1 = file.slice(0, 1024); // 前1024字节
const chunk2 = file.slice(1024, 2048); // 1024-2048字节
const chunk3 = file.slice(2048); // 从2048字节到最后
// 2. stream() - 获取文件流(现代浏览器)
async function processFileStream(file) {
const stream = file.stream();
const reader = stream.getReader();
let totalBytes = 0;
while (true) {
const { done, value } = await reader.read();
if (done) break;
totalBytes += value.length;
console.log(`已读取: ${totalBytes}/${file.size} 字节`);
}
}
// 3. arrayBuffer() - 异步获取 ArrayBuffer
async function getFileBuffer(file) {
const arrayBuffer = await file.arrayBuffer();
console.log('ArrayBuffer 大小:', arrayBuffer.byteLength);
return arrayBuffer;
}
// 4. text() - 异步读取为文本
async function readFileAsText(file) {
try {
const text = await file.text();
console.log('文件内容:', text);
return text;
} catch (error) {
console.error('读取失败:', error);
}
}
// 5. 一次性获取所有转换
async function processFile(file) {
// 并行获取不同格式
const [text, arrayBuffer] = await Promise.all([
file.text(),
file.arrayBuffer()
]);
return { text, arrayBuffer };
}
3、FileList API
filelist是一个类数组对象,它是file对象的集合。

filelist的不可变性:
- filelist只读,不能直接修改;
- 不能添加,删除,修改文件;
- 只能通过重新选择文件来改变。
(1)获取FileList对象
javascript
//1、从文件输入框获取
// 单个文件选择
//<input type="file" id="singleFile">
const singleInput = document.getElementById('singleFile');
singleInput.addEventListener('change', (e) => {
const fileList = e.target.files; // 即使单选,返回的也是 FileList
console.log('文件数量:', fileList.length); // 0 或 1
console.log('第一个文件:', fileList[0]);
});
// 多个文件选择
//<input type="file" multiple id="multiFile">
const multiInput = document.getElementById('multiFile');
multiInput.addEventListener('change', (e) => {
const fileList = e.target.files;
console.log('选择了', fileList.length, '个文件');
});
//2、从拖放操作获取
const dropZone = document.getElementById('dropZone');
dropZone.addEventListener('drop', (e) => {
e.preventDefault();
// 从 DataTransfer 获取 FileList
const fileList = e.dataTransfer.files;
console.log('拖放了', fileList.length, '个文件');
handleFiles(fileList);
});
// 必须阻止默认行为才能正常工作
dropZone.addEventListener('dragover', (e) => {
e.preventDefault();
e.dataTransfer.dropEffect = 'copy';
});
//3、从datatransfer获取
// 在粘贴事件中获取
document.addEventListener('paste', (e) => {
const items = e.clipboardData.items;
const files = [];
for (let i = 0; i < items.length; i++) {
if (items[i].kind === 'file') {
const file = items[i].getAsFile();
files.push(file);
}
}
// 注意:需要自己创建类 FileList 的对象
const fileList = {
length: files.length,
item: (index) => files[index],
[Symbol.iterator]: function*() {
for (let i = 0; i < this.length; i++) {
yield this[i];
}
}
};
// 添加索引访问
files.forEach((file, index) => {
fileList[index] = file;
});
return fileList;
});
(2)核心属性
javascript
const fileList = input.files;
// length - 文件数量
console.log('文件数量:', fileList.length);
// 索引访问 - 类数组特性
console.log('第一个文件:', fileList[0]);
console.log('第二个文件:', fileList[1]);
// 遍历所有属性
console.log('=== FileList 属性 ===');
for (let key in fileList) {
if (!isNaN(parseInt(key))) {
console.log(`索引 ${key}:`, fileList[key].name);
}
}
(3)核心方法
javascript
// item(index) - 通过索引获取文件
const firstFile = fileList.item(0);
const secondFile = fileList.item(1);
const invalidFile = fileList.item(999); // 返回 null
console.log('item(0):', firstFile);
console.log('item(1):', secondFile);
console.log('item(999):', invalidFile); // null
// 等价于 fileList[0],但更安全
if (fileList.item(0)) {
console.log('第一个文件存在');
}
(4)遍历FileList的方法
a、传统for循环
javascript
function processFilesTraditional(fileList) {
for (let i = 0; i < fileList.length; i++) {
const file = fileList[i];
console.log(`文件 ${i + 1}: ${file.name} (${file.size} 字节)`);
}
}
// 使用 item() 方法
for (let i = 0; i < fileList.length; i++) {
const file = fileList.item(i);
console.log(file.name);
}
b、for...of循环
javascript
function processFilesForOf(fileList) {
let index = 1;
for (const file of fileList) {
console.log(`文件 ${index}: ${file.name}`);
index++;
}
}
// 注意:FileList 是可迭代的,但不是数组
console.log(Symbol.iterator in fileList); // true,支持迭代
c、Array.from转换为数组
javascript
// 转换为真正的数组
const filesArray = Array.from(fileList);
// 现在可以使用数组的所有方法
filesArray.forEach((file, index) => {
console.log(`文件 ${index + 1}: ${file.name}`);
});
// 数组方法示例
const totalSize = filesArray.reduce((sum, file) => sum + file.size, 0);
const imageFiles = filesArray.filter(file => file.type.startsWith('image/'));
const fileNames = filesArray.map(file => file.name);
d、扩展运算符
javascript
// 使用扩展运算符转为数组
const files = [...fileList];
// 直接使用数组方法
[...fileList].forEach((file, index) => {
console.log(`文件 ${index + 1}:`, file.name);
});
// 统计信息
const stats = {
totalFiles: [...fileList].length,
totalSize: [...fileList].reduce((sum, file) => sum + file.size, 0),
imageCount: [...fileList].filter(f => f.type.startsWith('image/')).length
};
e、forEach方法
javascript
// FileList 没有原生的 forEach 方法
// ❌ 错误:fileList.forEach(...) // TypeError
// ✅ 正确:先转换为数组
Array.from(fileList).forEach((file, index) => {
console.log(`文件 ${index + 1}: ${file.name}`);
});
// 或者创建辅助函数
function forEachFile(fileList, callback) {
for (let i = 0; i < fileList.length; i++) {
callback(fileList[i], i, fileList);
}
}
// 使用辅助函数
forEachFile(fileList, (file, index) => {
console.log(`处理文件 ${index}: ${file.name}`);
});
(5)操作和修改FileList
a、添加文件到FileList
javascript
// FileList 是只读的,但可以通过 DataTransfer 创建新的
function addFileToInput(fileInput, newFile) {
const dt = new DataTransfer();
// 1. 添加现有的文件
const existingFiles = fileInput.files;
for (let i = 0; i < existingFiles.length; i++) {
dt.items.add(existingFiles[i]);
}
// 2. 添加新文件
dt.items.add(newFile);
// 3. 更新 file input 的 files 属性
fileInput.files = dt.files;
// 4. 触发 change 事件
fileInput.dispatchEvent(new Event('change'));
}
// 创建文件并添加
function createAndAddFile(fileInput, content, filename, type = 'text/plain') {
const blob = new Blob([content], { type });
const file = new File([blob], filename, {
type,
lastModified: Date.now()
});
addFileToInput(fileInput, file);
}
b、删除FileList文件
javascript
function removeFileFromInput(fileInput, indexToRemove) {
const dt = new DataTransfer();
const files = fileInput.files;
// 复制除了要删除的文件外的所有文件
for (let i = 0; i < files.length; i++) {
if (i !== indexToRemove) {
dt.items.add(files[i]);
}
}
// 更新 files
fileInput.files = dt.files;
// 触发事件
fileInput.dispatchEvent(new Event('change'));
return dt.files; // 返回新的 FileList
}
// 删除多个文件
function removeFilesFromInput(fileInput, indexesToRemove) {
const dt = new DataTransfer();
const files = fileInput.files;
const removeSet = new Set(indexesToRemove);
for (let i = 0; i < files.length; i++) {
if (!removeSet.has(i)) {
dt.items.add(files[i]);
}
}
fileInput.files = dt.files;
fileInput.dispatchEvent(new Event('change'));
return dt.files;
}
c、重新排序FileList
javascript
function reorderFileList(fileInput, newOrder) {
const dt = new DataTransfer();
const files = fileInput.files;
// 按照新顺序添加文件
newOrder.forEach(index => {
if (index >= 0 && index < files.length) {
dt.items.add(files[index]);
}
});
fileInput.files = dt.files;
fileInput.dispatchEvent(new Event('change'));
return dt.files;
}
// 移动文件位置
function moveFile(fileInput, fromIndex, toIndex) {
const files = Array.from(fileInput.files);
if (fromIndex < 0 || fromIndex >= files.length ||
toIndex < 0 || toIndex >= files.length) {
return fileInput.files;
}
// 移动数组元素
const [movedFile] = files.splice(fromIndex, 1);
files.splice(toIndex, 0, movedFile);
// 更新 FileList
const dt = new DataTransfer();
files.forEach(file => dt.items.add(file));
fileInput.files = dt.files;
fileInput.dispatchEvent(new Event('change'));
return dt.files;
}
4、showOpenFilePicker API
用于让用户通过系统原生的文件选择器选择一个或多个本地文件,并获取对这些文件的访问权限。
特点:
- 他返回一个promise,需要使用async/await或.then()处理结果;
- 访问文件完全由用户主动选择触发,页面不能静默读取整个硬盘,权限与浏览器标签页绑定;
- 他不直接返回文件file对象,而是返回FileSystemFileHandle句柄(数组),通过该句柄进一步操作文件;
(1)简单使用
javascript
const fileHandles = await window.showOpenFilePicker(options);
multiple参数:确定是单选模式还是多选模式
types参数:数组,用于允许选择的文件类型
(2)读取单个文件
javascript
async function readTextFile() {
try {
const [handle] = await showOpenFilePicker({
types: [{
description: '文本文件',
accept: {'text/plain': ['.txt']}
}]
});
const file = await handle.getFile();
const content = await file.text();
// 在页面显示内容
document.getElementById('content').textContent = content;
// 返回句柄以便后续操作
return handle;
} catch (err) {
if (err.name !== 'AbortError') {
alert('读取文件失败: ' + err.message);
}
}
}
(3)权限状态检测
javascript
async function checkPermission(handle) {
const options = { mode: 'read' };
const status = await handle.queryPermission(options);
if (status === 'granted') {
return true;
} else if (status === 'prompt') {
// 请求权限
return await handle.requestPermission(options) === 'granted';
}
return false;
}
(4)与<input type='file'>对比

5、showSaveFilePicker API
用于让用户选择保存位置并获取写入文件的权限。
特点:
- 返回一个filesystemfilehandle对象,允许向指定位置写入内容;
- 获取的是写权限,权限可持久化,返回promise。
(1)基本使用
javascript
// 保存文本内容
async function saveText() {
try {
// 1. 显示保存对话框
const handle = await window.showSaveFilePicker({
suggestedName: 'document.txt', // 建议的文件名
types: [{
description: '文本文件',
accept: { 'text/plain': ['.txt'] }
}]
});
// 2. 创建可写流
const writable = await handle.createWritable();
// 3. 写入内容
await writable.write('Hello, World!');
// 4. 关闭流
await writable.close();
console.log('文件保存成功!');
return handle;
} catch (err) {
if (err.name !== 'AbortError') {
console.error('保存失败:', err);
}
return null;
}
}
(2)配置选项
javascript
const options = {
// 建议的文件名(用户可以修改)
suggestedName: '未命名文档.txt',
// 默认开始位置(如"文档"、"下载"等文件夹)
startIn: 'downloads', // 或 'desktop', 'documents'
// 文件类型选项
types: [
{
description: '文本文档', // 用户看到的描述
accept: { // MIME类型映射
'text/plain': ['.txt', '.text'],
'text/html': ['.html', '.htm']
}
},
{
description: '图片文件',
accept: {
'image/png': ['.png'],
'image/jpeg': ['.jpg', '.jpeg']
}
}
],
// 是否排除"所有文件"选项
excludeAcceptAllOption: false
};
(3)基本写入流程
javascript
async function saveContent(content, filename, mimeType = 'text/plain') {
const handle = await showSaveFilePicker({
suggestedName: filename,
types: [{
description: '文件',
accept: { [mimeType]: [getExtension(mimeType)] }
}]
});
const writable = await handle.createWritable();
await writable.write(content);
await writable.close();
return handle;
}
// 使用示例
await saveContent(JSON.stringify(data, null, 2), 'config.json', 'application/json');
(4)检查写权限
javascript
async function checkWritePermission(handle) {
const options = { mode: 'readwrite' };
const status = await handle.queryPermission(options);
switch (status) {
case 'granted':
return true;
case 'prompt':
// 请求权限
return await handle.requestPermission(options) === 'granted';
case 'denied':
console.warn('写权限被拒绝');
return false;
default:
return false;
}
}
// 使用权限检查
async function writeToFile(handle, content) {
const hasPermission = await checkWritePermission(handle);
if (hasPermission) {
const writable = await handle.createWritable();
await writable.write(content);
await writable.close();
return true;
}
return false;
}
(5)与其配合的"另存为"功能
javascript
let currentFileHandle = null;
async function openFile() {
const [handle] = await showOpenFilePicker();
const file = await handle.getFile();
const content = await file.text();
// 保存句柄以便后续保存
currentFileHandle = handle;
return content;
}
async function saveFile(content) {
if (currentFileHandle) {
// 直接保存到原文件
const writable = await currentFileHandle.createWritable();
await writable.write(content);
await writable.close();
} else {
// 另存为新文件
currentFileHandle = await showSaveFilePicker({
suggestedName: '文档.txt'
});
const writable = await currentFileHandle.createWritable();
await writable.write(content);
await writable.close();
}
}
async function saveAs(content) {
// 总是显示"另存为"对话框
const handle = await showSaveFilePicker({
suggestedName: '文档.txt'
});
const writable = await handle.createWritable();
await writable.write(content);
await writable.close();
// 更新当前文件句柄
currentFileHandle = handle;
}
6、showDirectoryPicker API
是File System Access API的一部分,它允许网页安全地访问用户本地文件系统的目录(像在资源管理器中一样,打开整个文件夹)。
注:showdirectorypicker只有在HTTPS中可用
与传统文件上传对比:
- 传统文件上传:每次只能存/取一个文件,想要获取其他的文件,则需重新打开;
- showdirectorypicker:可以获得整个访问权限,可自由查看,可一次授权多测使用。
(1)基本用法
简单调用:

参数配置:


(2)核心对象和方法

权限系统:
注:一般在用户交互后使用,不在页面加载时立即请求

(3)实际应用
javascript
class BatchFileUploader {
constructor() {
this.dirHandle = null;
this.files = [];
}
// 选择目录
async selectDirectory() {
try {
this.dirHandle = await showDirectoryPicker({
id: 'upload-directory',
mode: 'read'
});
// 读取目录中的所有图片文件
await this.scanForImages();
return true;
} catch (err) {
console.error('选择目录失败:', err);
return false;
}
}
// 扫描目录中的图片
async scanForImages() {
this.files = [];
for await (const [name, handle] of this.dirHandle.entries()) {
if (handle.kind === 'file') {
const file = await handle.getFile();
// 只处理图片文件
if (file.type.startsWith('image/')) {
this.files.push({
name,
handle,
file,
size: file.size,
type: file.type,
lastModified: file.lastModified
});
}
}
}
console.log(`找到 ${this.files.length} 个图片文件`);
}
// 批量上传
async uploadAll() {
const results = [];
for (const fileInfo of this.files) {
try {
const result = await this.uploadFile(fileInfo);
results.push({ success: true, ...result });
} catch (error) {
results.push({ success: false, name: fileInfo.name, error });
}
}
return results;
}
async uploadFile(fileInfo) {
const formData = new FormData();
formData.append('file', fileInfo.file);
formData.append('path', fileInfo.name);
const response = await fetch('/api/upload', {
method: 'POST',
body: formData
});
return await response.json();
}
}
7、DataTransfer API
是浏览器提供的用于在拖放操作中传输数据(例如一个快递要从a处到b处,datatransfer对象就是快递包裹;拖放事件就相当于寄件,运输,收件的过程;拖放数据就相当于包裹中的物品)。
(1)对象详解
dropeffect:拖放操作(复制,移动,链接);
effectallowed:允许拖放的操作有哪些(权限设置)。


拖放的数据类型:

(2)基本使用
最简单的文本拖放
html
<!-- HTML结构 -->
<div id="source" draggable="true">拖拽我</div>
<div id="target">放到这里</div>
<script>
// JavaScript实现
const source = document.getElementById('source');
const target = document.getElementById('target');
// 1. 拖拽开始:准备数据
source.addEventListener('dragstart', (event) => {
console.log('开始拖拽');
// 设置要传输的数据
event.dataTransfer.setData('text/plain', '这是传输的数据');
// 设置拖拽效果
event.dataTransfer.effectAllowed = 'copy';
});
// 2. 拖拽到目标上方:允许放置
target.addEventListener('dragover', (event) => {
event.preventDefault(); // 必须调用,允许放置
event.dataTransfer.dropEffect = 'copy';
target.style.backgroundColor = '#e3f2fd';
});
// 3. 离开目标区域
target.addEventListener('dragleave', () => {
target.style.backgroundColor = '';
});
// 4. 放置数据
target.addEventListener('drop', (event) => {
event.preventDefault();
// 获取传输的数据
const data = event.dataTransfer.getData('text/plain');
target.textContent = `接收到: ${data}`;
target.style.backgroundColor = '';
console.log('数据放置完成:', data);
});
</script>
(3)文件拖放
html
<div id="dropZone" style="border: 2px dashed #ccc; padding: 50px;">
将文件拖放到这里
<div id="fileList"></div>
</div>
<script>
const dropZone = document.getElementById('dropZone');
const fileList = document.getElementById('fileList');
// 阻止浏览器默认行为(打开文件)
dropZone.addEventListener('dragover', (event) => {
event.preventDefault();
event.dataTransfer.dropEffect = 'copy';
dropZone.style.borderColor = '#2196f3';
});
dropZone.addEventListener('dragleave', () => {
dropZone.style.borderColor = '#ccc';
});
dropZone.addEventListener('drop', (event) => {
event.preventDefault();
dropZone.style.borderColor = '#4caf50';
// 获取拖放的文件
const files = event.dataTransfer.files;
console.log('拖放的文件数量:', files.length);
// 处理每个文件
Array.from(files).forEach((file, index) => {
const fileInfo = document.createElement('div');
fileInfo.innerHTML = `
<strong>${index + 1}. ${file.name}</strong><br>
类型: ${file.type || '未知'}<br>
大小: ${(file.size / 1024).toFixed(2)} KB<br>
修改时间: ${new Date(file.lastModified).toLocaleString()}
<hr>
`;
fileList.appendChild(fileInfo);
// 如果是图片,创建预览
if (file.type.startsWith('image/')) {
const reader = new FileReader();
reader.onload = (e) => {
const img = document.createElement('img');
img.src = e.target.result;
img.style.maxWidth = '200px';
img.style.maxHeight = '150px';
img.style.margin = '10px';
fileInfo.appendChild(img);
};
reader.readAsDataURL(file);
}
});
});
</script>
8、文件处理工具API:URLcreateObjectURL()
是一个浏览器api,它能将内存中的Blob,File,MediaSource对象转换成一个临时URL地址。
特点:
- 临时性:只在当前会话有效;
- 每个对象生成唯一的URL;
- 需要手动释放。
(1)基本语法

javascript
//简单示例
// 示例1:创建文本文件的下载链接
function createTextFileDownload() {
// 1. 创建Blob对象
const text = 'Hello, World! 这是要下载的内容。';
const blob = new Blob([text], { type: 'text/plain;charset=utf-8' });
// 2. 创建Object URL
const url = URL.createObjectURL(blob);
console.log('生成的URL:', url);
// 输出: blob:http://localhost:3000/123e4567-e89b-12d3-a456-426614174000
// 3. 使用URL创建下载链接
const a = document.createElement('a');
a.href = url;
a.download = 'example.txt'; // 下载文件名
a.textContent = '点击下载文本文件';
// 4. 添加到页面并自动点击
document.body.appendChild(a);
a.click();
// 5. 清理(重要!)
setTimeout(() => {
URL.revokeObjectURL(url);
document.body.removeChild(a);
console.log('URL已回收,内存已释放');
}, 100);
}
// 示例2:预览图片
function previewImage(file) {
// file是用户通过<input type="file">选择的File对象
// 1. 创建Object URL
const imageUrl = URL.createObjectURL(file);
// 2. 创建图片元素并显示
const img = document.createElement('img');
img.src = imageUrl;
img.style.maxWidth = '300px';
img.alt = file.name;
// 3. 添加到页面
const container = document.getElementById('preview-container');
container.innerHTML = ''; // 清空之前的预览
container.appendChild(img);
// 4. 图片加载完成后清理(可选,看需求)
img.onload = () => {
console.log('图片预览已显示,URL:', imageUrl);
// 注意:这里不立即revoke,因为图片还在显示
};
// 返回URL以便后续清理
return imageUrl;
}
(2)工作原理

(3)与Data URL对比
javascript
// 对比示例:同一个图片的两种不同表示
// Data URL方式(数据在URL中)
const dataUrl = `data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUA
AAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO
9TXL0Y4OHwAAAABJRU5ErkJggg==`;
// Blob URL方式(数据在内存中,URL是引用)
const blob = base64ToBlob(base64Data, 'image/png');
const blobUrl = URL.createObjectURL(blob);
// 比较表
const comparison = {
DataURL: {
存储方式: '数据内嵌在URL字符串中',
长度: '很长(数据越大URL越长)',
内存: '每个使用处都有一份完整副本',
更新: '需要重新生成整个URL',
适用: '小于10KB的小资源'
},
BlobURL: {
存储方式: '数据在内存中,URL是引用',
长度: '固定长度,与数据大小无关',
内存: '数据只有一份,多处引用',
更新: '更新Blob内容,URL不变',
适用: '任意大小的资源'
}
};
(4)实际应用
文件预览系统
javascript
class FilePreviewSystem {
constructor() {
this.activeUrls = new Set(); // 跟踪活跃的URL
this.previewHistory = []; // 预览历史记录
}
// 预览单个文件
previewFile(file) {
if (!file) return null;
// 清理之前的预览(如果需要)
this.cleanupPreviousPreviews();
// 创建Object URL
const url = URL.createObjectURL(file);
this.activeUrls.add(url);
// 根据文件类型选择预览方式
if (file.type.startsWith('image/')) {
this.previewImage(url, file.name);
} else if (file.type.startsWith('video/')) {
this.previewVideo(url, file.name);
} else if (file.type.startsWith('audio/')) {
this.previewAudio(url, file.name);
} else if (file.type === 'application/pdf') {
this.previewPDF(url, file.name);
} else {
this.previewAsText(url, file);
}
// 记录历史
this.previewHistory.push({
url,
fileName: file.name,
timestamp: Date.now(),
type: file.type
});
return url;
}
// 预览图片
previewImage(url, fileName) {
const container = document.getElementById('preview-container');
// 创建图片元素
const img = document.createElement('img');
img.src = url;
img.alt = fileName;
img.className = 'preview-image';
// 添加加载和错误处理
img.onload = () => {
console.log(`图片加载成功: ${fileName}`);
this.adjustPreviewSize(img);
};
img.onerror = () => {
console.error(`图片加载失败: ${fileName}`);
this.showErrorMessage('图片加载失败');
};
container.innerHTML = '';
container.appendChild(img);
// 添加清理回调
img.addEventListener('unload', () => {
this.revokeUrl(url);
});
}
// 预览视频
previewVideo(url, fileName) {
const container = document.getElementById('preview-container');
const video = document.createElement('video');
video.src = url;
video.controls = true;
video.className = 'preview-video';
// 添加元数据
const meta = document.createElement('div');
meta.className = 'video-meta';
meta.textContent = `视频: ${fileName}`;
container.innerHTML = '';
container.appendChild(video);
container.appendChild(meta);
}
// 批量预览文件
previewMultipleFiles(files) {
const urls = [];
const gallery = document.createElement('div');
gallery.className = 'file-gallery';
Array.from(files).forEach((file, index) => {
if (file.type.startsWith('image/')) {
const url = URL.createObjectURL(file);
urls.push(url);
this.activeUrls.add(url);
const img = document.createElement('img');
img.src = url;
img.alt = file.name;
img.className = 'gallery-thumb';
img.dataset.index = index;
// 点击查看大图
img.addEventListener('click', () => {
this.showFullscreenPreview(url, file.name);
});
gallery.appendChild(img);
}
});
const container = document.getElementById('preview-container');
container.innerHTML = '';
container.appendChild(gallery);
return urls;
}
// 清理资源
cleanupPreviousPreviews() {
// 可以根据策略决定是否立即清理
// 这里清理除当前活跃外的所有URL
this.activeUrls.forEach(url => {
URL.revokeObjectURL(url);
});
this.activeUrls.clear();
}
// 安全地撤销URL
revokeUrl(url) {
if (this.activeUrls.has(url)) {
URL.revokeObjectURL(url);
this.activeUrls.delete(url);
console.log(`已释放URL: ${url}`);
}
}
// 页面卸载时清理所有资源
setupCleanupOnUnload() {
window.addEventListener('beforeunload', () => {
this.cleanupAllResources();
});
// 防止内存泄漏
window.addEventListener('pagehide', () => {
this.cleanupAllResources();
});
}
cleanupAllResources() {
console.log('清理所有Blob URL资源');
this.activeUrls.forEach(url => {
try {
URL.revokeObjectURL(url);
} catch (e) {
console.warn('清理URL时出错:', e);
}
});
this.activeUrls.clear();
this.previewHistory = [];
}
}
9、Fetch API
是一个基于promise的JavaScript API,用于发起网络请求,代替了传统的XMLHttpRequest。
(1)基本用法
a、简单的GET请求
javascript
// 基础GET请求
fetch('https://api.example.com/data')
.then(response => {
if (!response.ok) {
throw new Error('网络响应不正常');
}
return response.json(); // 解析JSON响应
})
.then(data => {
console.log('获取到的数据:', data);
// 处理数据
})
.catch(error => {
console.error('请求失败:', error);
// 错误处理
});
// 实际示例:获取GitHub用户信息
async function getGitHubUser(username) {
try {
const response = await fetch(`https://api.github.com/users/${username}`);
if (response.status === 404) {
throw new Error('用户不存在');
}
if (!response.ok) {
throw new Error(`HTTP错误: ${response.status}`);
}
const userData = await response.json();
console.log(`${username}的信息:`, userData);
return userData;
} catch (error) {
console.error('获取用户信息失败:', error);
return null;
}
}
// 使用
getGitHubUser('octocat').then(user => {
if (user) {
console.log(`用户名: ${user.login}, 粉丝: ${user.followers}`);
}
});
b、Fetch配置选项
javascript
// Fetch的完整配置对象
const fetchOptions = {
// 请求方法
method: 'GET', // GET, POST, PUT, DELETE, PATCH, HEAD, OPTIONS
// 请求头
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer your-token-here',
'User-Agent': 'MyApp/1.0',
'Accept': 'application/json'
},
// 请求体(GET和HEAD请求不能有body)
body: JSON.stringify({ key: 'value' }),
// 请求模式
mode: 'cors', // cors, no-cors, same-origin
// 缓存策略
cache: 'default', // default, no-store, reload, no-cache, force-cache, only-if-cached
// 是否发送凭证(cookies)
credentials: 'same-origin', // omit, same-origin, include
// 重定向策略
redirect: 'follow', // follow, error, manual
// 引用策略
referrerPolicy: 'no-referrer-when-downgrade',
// 完整性校验
integrity: 'sha256-BpfBw7ivV8q2jLiT13fxDYAe2tJllusRSZ273h2nFSE=',
// 请求超时(注意:Fetch原生不支持timeout,需要AbortController)
signal: abortController.signal,
// 优先级
priority: 'auto' // high, low, auto
};
c、HTTP方法示例
javascript
class ApiClient {
// GET请求:获取数据
async getData(url, queryParams = {}) {
// 构建查询字符串
const queryString = new URLSearchParams(queryParams).toString();
const fullUrl = queryString ? `${url}?${queryString}` : url;
const response = await fetch(fullUrl, {
method: 'GET',
headers: {
'Accept': 'application/json'
}
});
return this.handleResponse(response);
}
// POST请求:创建资源
async createData(url, data) {
const response = await fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json'
},
body: JSON.stringify(data)
});
return this.handleResponse(response);
}
// PUT请求:更新整个资源
async updateData(url, data) {
const response = await fetch(url, {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json'
},
body: JSON.stringify(data)
});
return this.handleResponse(response);
}
// PATCH请求:部分更新
async patchData(url, partialData) {
const response = await fetch(url, {
method: 'PATCH',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json'
},
body: JSON.stringify(partialData)
});
return this.handleResponse(response);
}
// DELETE请求:删除资源
async deleteData(url) {
const response = await fetch(url, {
method: 'DELETE'
});
return this.handleResponse(response, false); // 可能没有响应体
}
// 处理响应
async handleResponse(response, expectJson = true) {
if (!response.ok) {
const errorText = await response.text();
throw new Error(`请求失败: ${response.status} - ${errorText}`);
}
if (response.status === 204) { // No Content
return null;
}
if (expectJson) {
return await response.json();
}
return response;
}
}
// 使用示例
const api = new ApiClient();
// 获取用户列表
api.getData('/api/users', { page: 1, limit: 10 })
.then(users => console.log('用户列表:', users));
// 创建新用户
api.createData('/api/users', {
name: '张三',
email: 'zhangsan@example.com',
age: 25
}).then(newUser => console.log('创建的用户:', newUser));
(2)Response对象详情
response对象的属性和方法
javascript
// 响应对象的结构
const response = {
// 属性
ok: true, // HTTP状态码在200-299范围内为true
status: 200, // HTTP状态码
statusText: 'OK', // 状态文本
url: 'https://api.example.com/data', // 请求的URL
redirected: false, // 是否被重定向
type: 'cors', // basic, cors, error, opaque, opaqueredirect
// Headers对象
headers: Headers {}, // 响应头
// 读取响应体的方法
arrayBuffer(): Promise<ArrayBuffer>,
blob(): Promise<Blob>,
formData(): Promise<FormData>,
json(): Promise<any>,
text(): Promise<string>,
// 克隆响应
clone(): Response,
// 其他
body: ReadableStream, // 响应体流
bodyUsed: false // 是否已读取过响应体
};
// 实际使用示例
async function processResponse(url) {
const response = await fetch(url);
console.log('状态码:', response.status);
console.log('状态文本:', response.statusText);
console.log('请求成功?', response.ok);
console.log('请求URL:', response.url);
console.log('是否重定向?', response.redirected);
console.log('响应类型:', response.type);
// 检查响应头
console.log('内容类型:', response.headers.get('Content-Type'));
console.log('内容长度:', response.headers.get('Content-Length'));
// 遍历所有响应头
response.headers.forEach((value, key) => {
console.log(`${key}: ${value}`);
});
// 根据内容类型选择解析方式
const contentType = response.headers.get('Content-Type');
let data;
if (contentType.includes('application/json')) {
data = await response.json();
} else if (contentType.includes('text/')) {
data = await response.text();
} else if (contentType.includes('image/')) {
data = await response.blob();
} else if (contentType.includes('multipart/form-data')) {
data = await response.formData();
} else {
data = await response.arrayBuffer();
}
return data;
}
(3)实际应用
a、上传文件
javascript
class FileUploader {
constructor() {
this.chunkSize = 1024 * 1024; // 1MB分片
}
// 1. 简单文件上传
async uploadFile(file, uploadUrl) {
const formData = new FormData();
formData.append('file', file);
formData.append('fileName', file.name);
formData.append('fileSize', file.size);
formData.append('uploadTime', new Date().toISOString());
const response = await fetch(uploadUrl, {
method: 'POST',
body: formData
// 注意:使用FormData时不要设置Content-Type头,浏览器会自动设置
});
return response.json();
}
// 2. 大文件分片上传
async uploadLargeFile(file, uploadUrl, onProgress) {
const fileSize = file.size;
const totalChunks = Math.ceil(fileSize / this.chunkSize);
const fileId = `${Date.now()}_${Math.random().toString(36).substr(2)}`;
let uploadedChunks = 0;
for (let chunkIndex = 0; chunkIndex < totalChunks; chunkIndex++) {
const start = chunkIndex * this.chunkSize;
const end = Math.min(start + this.chunkSize, fileSize);
const chunk = file.slice(start, end);
const formData = new FormData();
formData.append('fileId', fileId);
formData.append('chunkIndex', chunkIndex);
formData.append('totalChunks', totalChunks);
formData.append('fileName', file.name);
formData.append('chunk', chunk);
try {
const response = await fetch(`${uploadUrl}/chunk`, {
method: 'POST',
body: formData
});
if (!response.ok) {
throw new Error(`分片上传失败: ${response.status}`);
}
uploadedChunks++;
// 更新进度
if (onProgress) {
const progress = (uploadedChunks / totalChunks) * 100;
onProgress(progress, chunkIndex, totalChunks);
}
console.log(`分片 ${chunkIndex + 1}/${totalChunks} 上传成功`);
} catch (error) {
console.error(`分片 ${chunkIndex} 上传失败:`, error);
// 可以选择重试或放弃
throw error;
}
}
// 通知服务器合并分片
const mergeResponse = await fetch(`${uploadUrl}/merge`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
fileId,
fileName: file.name,
totalChunks
})
});
return mergeResponse.json();
}
// 3. 支持拖放上传
setupDropZone(dropZoneElement, onFileSelect) {
dropZoneElement.addEventListener('dragover', (event) => {
event.preventDefault();
dropZoneElement.classList.add('drag-over');
});
dropZoneElement.addEventListener('dragleave', () => {
dropZoneElement.classList.remove('drag-over');
});
dropZoneElement.addEventListener('drop', (event) => {
event.preventDefault();
dropZoneElement.classList.remove('drag-over');
const files = event.dataTransfer.files;
if (files.length > 0 && onFileSelect) {
onFileSelect(files[0]);
}
});
}
// 4. 带进度显示的上传
async uploadWithProgress(file, uploadUrl) {
const xhr = new XMLHttpRequest(); // 注意:这里用XHR为了进度事件
return new Promise((resolve, reject) => {
const formData = new FormData();
formData.append('file', file);
xhr.upload.addEventListener('progress', (event) => {
if (event.lengthComputable) {
const percentComplete = (event.loaded / event.total) * 100;
console.log(`上传进度: ${percentComplete.toFixed(2)}%`);
this.updateProgressBar(percentComplete);
}
});
xhr.addEventListener('load', () => {
if (xhr.status >= 200 && xhr.status < 300) {
resolve(JSON.parse(xhr.responseText));
} else {
reject(new Error(`上传失败: ${xhr.status}`));
}
});
xhr.addEventListener('error', () => {
reject(new Error('网络错误'));
});
xhr.addEventListener('abort', () => {
reject(new Error('上传被取消'));
});
xhr.open('POST', uploadUrl);
xhr.send(formData);
});
}
}
// 使用示例
const uploader = new FileUploader();
// 简单上传
document.getElementById('fileInput').addEventListener('change', async (event) => {
const file = event.target.files[0];
if (!file) return;
try {
const result = await uploader.uploadFile(file, '/api/upload');
console.log('上传成功:', result);
} catch (error) {
console.error('上传失败:', error);
}
});
// 大文件分片上传
async function uploadLargeFile() {
const file = document.getElementById('largeFile').files[0];
uploader.uploadLargeFile(file, '/api/upload', (progress) => {
document.getElementById('progress').value = progress;
document.getElementById('progressText').textContent = `${progress.toFixed(1)}%`;
}).then(result => {
console.log('大文件上传完成:', result);
}).catch(error => {
console.error('大文件上传失败:', error);
});
}
b、请求超时控制
javascript
// Fetch原生不支持timeout,需要使用AbortController
class FetchWithTimeout {
constructor(defaultTimeout = 10000) {
this.defaultTimeout = defaultTimeout;
}
// 带超时的fetch
async fetchWithTimeout(url, options = {}) {
const { timeout = this.defaultTimeout, ...fetchOptions } = options;
// 创建AbortController
const controller = new AbortController();
const { signal } = controller;
// 设置超时定时器
const timeoutId = setTimeout(() => {
controller.abort();
console.warn(`请求超时: ${url}, 超时时间: ${timeout}ms`);
}, timeout);
try {
const response = await fetch(url, {
...fetchOptions,
signal
});
clearTimeout(timeoutId);
if (!response.ok) {
throw new Error(`HTTP错误: ${response.status}`);
}
return response;
} catch (error) {
clearTimeout(timeoutId);
if (error.name === 'AbortError') {
throw new Error(`请求超时 (${timeout}ms)`);
}
throw error;
}
}
// 使用示例
async getDataWithTimeout() {
try {
const response = await this.fetchWithTimeout('https://api.example.com/slow-data', {
timeout: 5000, // 5秒超时
headers: {
'Accept': 'application/json'
}
});
return await response.json();
} catch (error) {
console.error('请求失败:', error.message);
return null;
}
}
}
// 批量请求,单个失败不影响其他
class BatchFetcher {
constructor(maxConcurrent = 3) {
this.maxConcurrent = maxConcurrent;
this.pendingRequests = new Set();
}
async fetchAll(urls, options = {}) {
const results = [];
const urlQueue = [...urls];
while (urlQueue.length > 0 || this.pendingRequests.size > 0) {
// 填充并发请求
while (this.pendingRequests.size < this.maxConcurrent && urlQueue.length > 0) {
const url = urlQueue.shift();
const request = this.fetchSingle(url, options);
this.pendingRequests.add(request);
request.finally(() => {
this.pendingRequests.delete(request);
});
}
// 等待至少一个请求完成
if (this.pendingRequests.size > 0) {
const completed = await Promise.race(this.pendingRequests);
results.push(completed);
}
}
return results;
}
async fetchSingle(url, options) {
try {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), 10000);
const response = await fetch(url, {
...options,
signal: controller.signal
});
clearTimeout(timeoutId);
if (!response.ok) {
return { url, success: false, error: `HTTP ${response.status}` };
}
const data = await response.json();
return { url, success: true, data };
} catch (error) {
return {
url,
success: false,
error: error.name === 'AbortError' ? '请求超时' : error.message
};
}
}
}
// 使用
const batchFetcher = new BatchFetcher(5);
const urls = [
'https://api.example.com/data1',
'https://api.example.com/data2',
// ... 更多URL
];
batchFetcher.fetchAll(urls).then(results => {
const successful = results.filter(r => r.success);
const failed = results.filter(r => !r.success);
console.log(`成功: ${successful.length}, 失败: ${failed.length}`);
});

