JavaScript文件的操作方法

一、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}`);
});
相关推荐
Trae1ounG2 小时前
模块间通信解耦
javascript
2301_797312262 小时前
学习Java42天
java·开发语言·学习
2501_944526422 小时前
Flutter for OpenHarmony 万能游戏库App实战 - 知识问答游戏实现
android·开发语言·javascript·python·flutter·游戏·harmonyos
chilavert3182 小时前
技术演进中的开发沉思-325 JVM:java体系技术全貌(下)
java·开发语言·jvm
chilavert3182 小时前
技术演进中的开发沉思-324 JVM:java技术体系全貌(上)
java·开发语言
卿着飞翔2 小时前
Vue使用yarn进行管理
前端·javascript·vue.js
夏天想2 小时前
vue通过iframe引入一个外链地址,怎么保证每次切换回这个已经打开的tab页的时候iframe不会重新加载
前端·javascript·vue.js
CCPC不拿奖不改名2 小时前
python基础面试编程题汇总+个人练习(入门+结构+函数+面向对象编程)--需要自取
开发语言·人工智能·python·学习·自然语言处理·面试·职场和发展
2501_944424122 小时前
Flutter for OpenHarmony游戏集合App实战之数字拼图滑动交换
android·开发语言·flutter·游戏·harmonyos