最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

JavaScript文件的操作方法總結(jié)大全

 更新時(shí)間:2026年05月22日 09:35:05   作者:吃吃喝喝小朋友  
在JavaScript中,我們可以通過文件操作來讀取和寫入文件,文件操作是Web開發(fā)中常見的任務(wù)之一,它使我們能夠處理本地文件、上傳文件以及與服務(wù)器進(jìn)行文件交互,這篇文章主要介紹了JavaScript文件的操作方法的相關(guān)資料,需要的朋友可以參考下

一、file對(duì)象基本屬性

1、獲取文件對(duì)象

// 通過文件輸入框獲取
const fileInput = document.getElementById('fileInput');
const file = fileInput.files[0];

// 通過拖拽獲取
dropZone.addEventListener('drop', (e) => {
    e.preventDefault();
    const file = e.dataTransfer.files[0];
});

2、file對(duì)象的一些屬性

if (file) {
    console.log('文件名:', file.name);          // "example.jpg"
    console.log('文件大小:', file.size);        // 字節(jié)數(shù)
    console.log('文件類型:', file.type);        // "image/jpeg"
    console.log('最后修改時(shí)間:', file.lastModified); // 時(shí)間戳
    console.log('文件對(duì)象:', file);            // File 對(duì)象本身
}

二、文件讀取方法

1、FileReader API

瀏覽器提供的用于異步讀取用戶計(jì)算機(jī)文件內(nèi)容,主要用于通過<input type="file">或拖放操作選擇的文件

(1)核心功能:

  • 異步讀取文件(不阻塞主線程);
  • 有文本,dataurl,arraybuffer,二進(jìn)制字符串四種讀取格式;
  • 通過事件監(jiān)聽處理讀取結(jié)果;
  • 只能讀取用戶明確選擇的文件。

(2)使用方法以及讀取方式

// 創(chuàng)建 FileReader 實(shí)例
const reader = new FileReader();

// 事件監(jiān)聽
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(`讀取進(jìn)度: ${percent}%`);
    }
};

三種讀取方法

//readAsText()  -----讀取為文本
reader.readAsText(file,encoding="UTF-8");
reader.onloand=function(){
    //處理文本內(nèi)容
    const text=reader.result;
    console.log("文本內(nèi)容:",text);
};
    
//readAsDataURL()   -----讀取為DataURL
reader.readAsDataURL(file)   //適合圖片預(yù)覽
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);//處理二進(jìn)制數(shù)據(jù)
reader.onload=function(){
    const buffer=reader.result;
    //處理二進(jìn)制數(shù)據(jù)
    const view=new DataView(buffer);
    console.log('第一個(gè)字節(jié):',view.getUint8(0));
    //轉(zhuǎn)換為其他格式
    const blob=new Blob([buffer]);
    const text=new TextDecoder().decode(buffer);
    };

filereader使用事件驅(qū)動(dòng)模型

    const reader = new FileReader();
    const file =someFile;
    //1、開始讀取時(shí)觸發(fā)
    reader.onloadstart=function(event){
        console.log("開始讀取文件...");
        document.getElementById('progress').textContent="讀取中..."
    };
    //2、讀取過程中觸發(fā)(用于跟蹤進(jìn)度)
    reader.onprogress=function (event){
        if(event.lengthComputable){
            const percent=(event.loaded/event.total)*100;
            console.log(`讀取進(jìn)度:${percent.toFixed(2)}`);
            document.getElementById('progress').textContent=`${percent.toFixed(2)}`;
        }
    };
    //3、讀取完成時(shí)觸發(fā)
    reader.onload=function(event){
        console.log('讀取完成!');
        const result=event.target.result;
    };
    //4、讀取失敗觸發(fā)
    reader.onerror=function (event){
        console.log('讀取錯(cuò)誤',reader.error);
        document.getElementById('error').textContent=`${reader.error.name}`;
    };
    //5、讀取結(jié)束(無論成功與失敗都會(huì)觸發(fā))
    reader.onloadend=function(event){
        console.log("讀取結(jié)束");
        document.getElementById('progress').textContent="完成";
    };
    //6、讀取中止時(shí)觸發(fā)
    reader.onabort = (event) => {
        console.log('讀取被中止');
};
    reader.readAsText(file);

三、文件驗(yàn)證方法

1、基本驗(yàn)證

function validateFile(file) {
    // 檢查是否選擇了文件
    if (!file) {
        return { valid: false, error: '請(qǐng)選擇文件' };
    }
    
    // 檢查文件大?。ɡ缦拗茷?MB)
    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: '不支持的文件類型' };
    }
    
    // 檢查文件名長(zhǎng)度
    if (file.name.length > 100) {
        return { valid: false, error: '文件名過長(zhǎng)' };
    }
    
    return { valid: true, error: null };
}

2、圖片文件專門驗(yàn)證

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、切片上傳(文件過大)

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、壓縮處理

// 圖片壓縮
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');
                
                // 計(jì)算新尺寸
                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對(duì)象

是JavaScript表示原始二進(jìn)制數(shù)據(jù)的對(duì)象,它可以存儲(chǔ)文本,圖像,音頻,視頻等各類型的數(shù)據(jù)。

blob的特點(diǎn):

  • 是一個(gè)二進(jìn)制容器可以存儲(chǔ)任何類型的數(shù)據(jù)(就像是一個(gè)數(shù)據(jù)快遞箱可以將任何原始數(shù)據(jù)打包成一個(gè)整體,并且為該快遞箱打上標(biāo)簽);
  • 創(chuàng)建后內(nèi)容不能修改(但可切片創(chuàng)建新的);
  • 創(chuàng)建時(shí)通常只是創(chuàng)建對(duì)原始數(shù)據(jù)的引用,不會(huì)立即復(fù)制數(shù)據(jù),只有實(shí)際讀取時(shí)才處理;
  • 可用于文件處理,數(shù)據(jù)存儲(chǔ),網(wǎng)絡(luò)傳輸?shù)取?/li>

(1)基本創(chuàng)建方式

// 方法1:使用 Blob 構(gòu)造函數(shù)
const blob1 = new Blob(['文本內(nèi)容'], {
    type: 'text/plain'  // MIME 類型
});

// 方法2:從字符串創(chuàng)建
const text = '這是一段文本';
const blob2 = new Blob([text], { type: 'text/plain;charset=utf-8' });

// 方法3:從數(shù)組緩沖區(qū)創(chuàng)建
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:混合不同類型的數(shù)據(jù)
const blob4 = new Blob([
    '文本部分\n',
    new Uint8Array([65, 66, 67]), // ABC
    '\n結(jié)束'
], { type: 'text/plain' });

(2)Blob的核心屬性

const blob = new Blob(['Hello, World!'], { 
    type: 'text/plain',
    endings: 'transparent' // 可選:native, transparent
});

console.log('=== Blob 屬性 ===');
console.log('大小(字節(jié)):', blob.size);        // 13
console.log('MIME 類型:', blob.type);          // "text/plain"
console.log('是否已關(guān)閉:', blob.isClosed);      // false(非標(biāo)準(zhǔn))
console.log('對(duì)象本身:', blob);                // Blob {size: 13, type: "text/plain"}

(3)核心方法

對(duì)于Blob能使用分片功能說明它是由頭部以及數(shù)據(jù)部分組成的(由應(yīng)用層實(shí)現(xiàn)),blob的分片頭部:

并且在每個(gè)分片上傳后都有HTTP響應(yīng);所有分片上傳后,服務(wù)器驗(yàn)證并確認(rèn)。

//slice() 切片
const blob = new Blob(['0123456789ABCDEF'], { type: 'text/plain' });

// 切片使用方式
const slice1 = blob.slice();           // 整個(gè) Blob 的副本
const slice2 = blob.slice(0, 5);       // 前5字節(jié)
const slice3 = blob.slice(5, 10);      // 5-10字節(jié)
const slice4 = blob.slice(-5);         // 最后5字節(jié)
const slice5 = blob.slice(0, -5);      // 除了最后5字節(jié)
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) {
    // 創(chuàng)建可讀流
    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} 字節(jié),累計(jì) ${totalBytes}`);
    }
    
    reader.releaseLock();
    console.log('流處理完成');
}

// 使用
const blob = new Blob(['a'.repeat(1000)]);
processBlobStream(blob);



//arrayBufer() 轉(zhuǎn)換為 ArrayBuffer
async function blobToArrayBuffer(blob) {
    const arrayBuffer = await blob.arrayBuffer();
    console.log('ArrayBuffer 大小:', arrayBuffer.byteLength);
    
    // 可以進(jìn)一步處理
    const uint8Array = new Uint8Array(arrayBuffer);
    const dataView = new DataView(arrayBuffer);
    
    return arrayBuffer;
}

//text()  讀取為文本
async function blobToText(blob) {
    const text = await blob.text();
    console.log('文本內(nèi)容:', 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:內(nèi)容直接包含在url中,自然包含,不需要額外文件;

  • blob url:只是一個(gè)引用/指針

兩者區(qū)別:

// 創(chuàng)建 Blob URL
function createBlobURL(blob) {
    const blobURL = URL.createObjectURL(blob);
    console.log('創(chuàng)建的 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 轉(zhuǎn) 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 轉(zhuǎn) 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 長(zhǎng)度:', blobURL.length);   // 較短
    console.log('Data URL 長(zhǎng)度:', dataURL.length);   // 較長(zhǎng)(base64編碼)
    console.log('Blob URL 前綴:', blobURL.substring(0, 20));
    console.log('Data URL 前綴:', dataURL.substring(0, 50));
    
    // 釋放資源
    URL.revokeObjectURL(blobURL);
    
    return { blobURL, dataURL };
}

(5)轉(zhuǎn)換矩陣

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對(duì)象是特殊的blob對(duì)象,表示用戶選擇的文件。

(1)file對(duì)象結(jié)構(gòu)

// File 對(duì)象結(jié)構(gòu)
const file = {
    name: "example.jpg",        // 文件名
    size: 102400,              // 文件大小(字節(jié))
    type: "image/jpeg",        // MIME 類型
    lastModified: 1672502400000, // 最后修改時(shí)間戳
    lastModifiedDate: Date,    // 最后修改日期對(duì)象
    webkitRelativePath: ""     // 相對(duì)路徑(webkit瀏覽器)
};

(2)獲取file對(duì)象的方式

文本輸入框方式

<input type="file" id="fileInput">

// 獲取單個(gè)文件
const fileInput = document.getElementById('fileInput');
const singleFile = fileInput.files[0];

// 獲取多個(gè)文件
//<input type="file" id="multiFileInput" multiple>

const multiFileInput = document.getElementById('multiFileInput');
const fileList = multiFileInput.files; // FileList 對(duì)象
const filesArray = Array.from(fileList); // 轉(zhuǎn)為數(shù)組

// 監(jiān)聽文件變化
fileInput.addEventListener('change', (e) => {
    const file = e.target.files[0];
    console.log('選擇的文件:', file);
});

拖拽獲取

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);
    }
});

// 阻止默認(rèn)拖放行為
['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => {
    dropZone.addEventListener(eventName, (e) => {
        e.preventDefault();
        e.stopPropagation();
    });
});

文件選擇器api

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)核心屬性

function inspectFile(file) {
    console.log('=== 文件信息 ===');
    console.log('文件名:', file.name);                 // "example.jpg"
    console.log('文件擴(kuò)展名:', file.name.split('.').pop()); // "jpg"
    console.log('文件大小:', file.size, '字節(jié)');        // 102400
    console.log('格式化大小:', formatFileSize(file.size)); // "100 KB"
    console.log('MIME類型:', file.type);               // "image/jpeg"
    console.log('最后修改時(shí)間戳:', file.lastModified);   // 1672502400000
    console.log('最后修改日期:', new Date(file.lastModified));
    
    // 瀏覽器特定屬性
    console.log('相對(duì)路徑:', 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的方法

// File 繼承自 Blob,所以可以使用 Blob 的所有方法
const file = someFile;

// 1. slice() - 切片文件
const chunk1 = file.slice(0, 1024);        // 前1024字節(jié)
const chunk2 = file.slice(1024, 2048);     // 1024-2048字節(jié)
const chunk3 = file.slice(2048);           // 從2048字節(jié)到最后

// 2. stream() - 獲取文件流(現(xiàn)代瀏覽器)
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} 字節(jié)`);
    }
}

// 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('文件內(nèi)容:', text);
        return text;
    } catch (error) {
        console.error('讀取失敗:', error);
    }
}

// 5. 一次性獲取所有轉(zhuǎn)換
async function processFile(file) {
    // 并行獲取不同格式
    const [text, arrayBuffer] = await Promise.all([
        file.text(),
        file.arrayBuffer()
    ]);
    
    return { text, arrayBuffer };
}

3、FileList API

filelist是一個(gè)類數(shù)組對(duì)象,它是file對(duì)象的集合。

filelist的不可變性:

  • filelist只讀,不能直接修改;
  • 不能添加,刪除,修改文件;
  • 只能通過重新選擇文件來改變。

(1)獲取FileList對(duì)象

//1、從文件輸入框獲取
// 單個(gè)文件選擇
//<input type="file" id="singleFile">

const singleInput = document.getElementById('singleFile');
singleInput.addEventListener('change', (e) => {
    const fileList = e.target.files; // 即使單選,返回的也是 FileList
    console.log('文件數(shù)量:', fileList.length); // 0 或 1
    console.log('第一個(gè)文件:', fileList[0]);
});

// 多個(gè)文件選擇
//<input type="file" multiple id="multiFile">

const multiInput = document.getElementById('multiFile');
multiInput.addEventListener('change', (e) => {
    const fileList = e.target.files;
    console.log('選擇了', fileList.length, '個(gè)文件');
});


//2、從拖放操作獲取
const dropZone = document.getElementById('dropZone');

dropZone.addEventListener('drop', (e) => {
    e.preventDefault();
    
    // 從 DataTransfer 獲取 FileList
    const fileList = e.dataTransfer.files;
    
    console.log('拖放了', fileList.length, '個(gè)文件');
    handleFiles(fileList);
});

// 必須阻止默認(rèn)行為才能正常工作
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);
        }
    }
    
    // 注意:需要自己創(chuàng)建類 FileList 的對(duì)象
    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)核心屬性

const fileList = input.files;

// length - 文件數(shù)量
console.log('文件數(shù)量:', fileList.length);

// 索引訪問 - 類數(shù)組特性
console.log('第一個(gè)文件:', fileList[0]);
console.log('第二個(gè)文件:', fileList[1]);

// 遍歷所有屬性
console.log('=== FileList 屬性 ===');
for (let key in fileList) {
    if (!isNaN(parseInt(key))) {
        console.log(`索引 ${key}:`, fileList[key].name);
    }
}

(3)核心方法

// 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

// 等價(jià)于 fileList[0],但更安全
if (fileList.item(0)) {
    console.log('第一個(gè)文件存在');
}

(4)遍歷FileList的方法

a、傳統(tǒng)for循環(huán)

function processFilesTraditional(fileList) {
    for (let i = 0; i < fileList.length; i++) {
        const file = fileList[i];
        console.log(`文件 ${i + 1}: ${file.name} (${file.size} 字節(jié))`);
    }
}

// 使用 item() 方法
for (let i = 0; i < fileList.length; i++) {
    const file = fileList.item(i);
    console.log(file.name);
}

b、for...of循環(huán)

function processFilesForOf(fileList) {
    let index = 1;
    for (const file of fileList) {
        console.log(`文件 ${index}: ${file.name}`);
        index++;
    }
}

// 注意:FileList 是可迭代的,但不是數(shù)組
console.log(Symbol.iterator in fileList); // true,支持迭代

c、Array.from轉(zhuǎn)換為數(shù)組

// 轉(zhuǎn)換為真正的數(shù)組
const filesArray = Array.from(fileList);

// 現(xiàn)在可以使用數(shù)組的所有方法
filesArray.forEach((file, index) => {
    console.log(`文件 ${index + 1}: ${file.name}`);
});

// 數(shù)組方法示例
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、擴(kuò)展運(yùn)算符

// 使用擴(kuò)展運(yùn)算符轉(zhuǎn)為數(shù)組
const files = [...fileList];

// 直接使用數(shù)組方法
[...fileList].forEach((file, index) => {
    console.log(`文件 ${index + 1}:`, file.name);
});

// 統(tǒng)計(jì)信息
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方法

// FileList 沒有原生的 forEach 方法
// ? 錯(cuò)誤:fileList.forEach(...) // TypeError

// ? 正確:先轉(zhuǎn)換為數(shù)組
Array.from(fileList).forEach((file, index) => {
    console.log(`文件 ${index + 1}: ${file.name}`);
});

// 或者創(chuàng)建輔助函數(shù)
function forEachFile(fileList, callback) {
    for (let i = 0; i < fileList.length; i++) {
        callback(fileList[i], i, fileList);
    }
}

// 使用輔助函數(shù)
forEachFile(fileList, (file, index) => {
    console.log(`處理文件 ${index}: ${file.name}`);
});

(5)操作和修改FileList

a、添加文件到FileList

// FileList 是只讀的,但可以通過 DataTransfer 創(chuàng)建新的
function addFileToInput(fileInput, newFile) {
    const dt = new DataTransfer();
    
    // 1. 添加現(xiàn)有的文件
    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. 觸發(fā) change 事件
    fileInput.dispatchEvent(new Event('change'));
}

// 創(chuàng)建文件并添加
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文件

function removeFileFromInput(fileInput, indexToRemove) {
    const dt = new DataTransfer();
    const files = fileInput.files;
    
    // 復(fù)制除了要?jiǎng)h除的文件外的所有文件
    for (let i = 0; i < files.length; i++) {
        if (i !== indexToRemove) {
            dt.items.add(files[i]);
        }
    }
    
    // 更新 files
    fileInput.files = dt.files;
    
    // 觸發(fā)事件
    fileInput.dispatchEvent(new Event('change'));
    
    return dt.files; // 返回新的 FileList
}

// 刪除多個(gè)文件
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

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;
}

// 移動(dòng)文件位置
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;
    }
    
    // 移動(dòng)數(shù)組元素
    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

用于讓用戶通過系統(tǒng)原生的文件選擇器選擇一個(gè)或多個(gè)本地文件,并獲取對(duì)這些文件的訪問權(quán)限。

特點(diǎn):

  • 他返回一個(gè)promise,需要使用async/await或.then()處理結(jié)果;
  • 訪問文件完全由用戶主動(dòng)選擇觸發(fā),頁面不能靜默讀取整個(gè)硬盤,權(quán)限與瀏覽器標(biāo)簽頁綁定;
  • 他不直接返回文件file對(duì)象,而是返回FileSystemFileHandle句柄(數(shù)組),通過該句柄進(jìn)一步操作文件;

(1)簡(jiǎn)單使用

const fileHandles = await window.showOpenFilePicker(options);

multiple參數(shù):確定是單選模式還是多選模式

types參數(shù):數(shù)組,用于允許選擇的文件類型

(2)讀取單個(gè)文件

async function readTextFile() {
  try {
    const [handle] = await showOpenFilePicker({
      types: [{
        description: '文本文件',
        accept: {'text/plain': ['.txt']}
      }]
    });
    
    const file = await handle.getFile();
    const content = await file.text();
    
    // 在頁面顯示內(nèi)容
    document.getElementById('content').textContent = content;
    
    // 返回句柄以便后續(xù)操作
    return handle;
  } catch (err) {
    if (err.name !== 'AbortError') {
      alert('讀取文件失敗: ' + err.message);
    }
  }
}

(3)權(quán)限狀態(tài)檢測(cè)

async function checkPermission(handle) {
  const options = { mode: 'read' }; 
  const status = await handle.queryPermission(options);
  
  if (status === 'granted') {
    return true;
  } else if (status === 'prompt') {
    // 請(qǐng)求權(quán)限
    return await handle.requestPermission(options) === 'granted';
  }
  
  return false;
}

(4)與<input type='file'>對(duì)比

5、showSaveFilePicker API

用于讓用戶選擇保存位置并獲取寫入文件的權(quán)限。

特點(diǎn):

  • 返回一個(gè)filesystemfilehandle對(duì)象,允許向指定位置寫入內(nèi)容;
  • 獲取的是寫權(quán)限,權(quán)限可持久化,返回promise。

(1)基本使用

// 保存文本內(nèi)容
async function saveText() {
  try {
    // 1. 顯示保存對(duì)話框
    const handle = await window.showSaveFilePicker({
      suggestedName: 'document.txt',  // 建議的文件名
      types: [{
        description: '文本文件',
        accept: { 'text/plain': ['.txt'] }
      }]
    });
    
    // 2. 創(chuàng)建可寫流
    const writable = await handle.createWritable();
    
    // 3. 寫入內(nèi)容
    await writable.write('Hello, World!');
    
    // 4. 關(guān)閉流
    await writable.close();
    
    console.log('文件保存成功!');
    return handle;
  } catch (err) {
    if (err.name !== 'AbortError') {
      console.error('保存失敗:', err);
    }
    return null;
  }
}

(2)配置選項(xiàng)

const options = {
  // 建議的文件名(用戶可以修改)
  suggestedName: '未命名文檔.txt',
  
  // 默認(rèn)開始位置(如"文檔"、"下載"等文件夾)
  startIn: 'downloads', // 或 'desktop', 'documents'
  
  // 文件類型選項(xiàng)
  types: [
    {
      description: '文本文檔',  // 用戶看到的描述
      accept: {                // MIME類型映射
        'text/plain': ['.txt', '.text'],
        'text/html': ['.html', '.htm']
      }
    },
    {
      description: '圖片文件',
      accept: {
        'image/png': ['.png'],
        'image/jpeg': ['.jpg', '.jpeg']
      }
    }
  ],
  
  // 是否排除"所有文件"選項(xiàng)
  excludeAcceptAllOption: false
};

(3)基本寫入流程

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)檢查寫權(quán)限

async function checkWritePermission(handle) {
  const options = { mode: 'readwrite' };
  const status = await handle.queryPermission(options);
  
  switch (status) {
    case 'granted':
      return true;
    case 'prompt':
      // 請(qǐng)求權(quán)限
      return await handle.requestPermission(options) === 'granted';
    case 'denied':
      console.warn('寫權(quán)限被拒絕');
      return false;
    default:
      return false;
  }
}

// 使用權(quán)限檢查
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)與其配合的“另存為”功能

let currentFileHandle = null;

async function openFile() {
  const [handle] = await showOpenFilePicker();
  const file = await handle.getFile();
  const content = await file.text();
  
  // 保存句柄以便后續(xù)保存
  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) {
  // 總是顯示"另存為"對(duì)話框
  const handle = await showSaveFilePicker({
    suggestedName: '文檔.txt'
  });
  const writable = await handle.createWritable();
  await writable.write(content);
  await writable.close();
  
  // 更新當(dāng)前文件句柄
  currentFileHandle = handle;
}

6、showDirectoryPicker API

是File System Access API的一部分,它允許網(wǎng)頁安全地訪問用戶本地文件系統(tǒng)的目錄(像在資源管理器中一樣,打開整個(gè)文件夾)。

注:showdirectorypicker只有在HTTPS中可用

與傳統(tǒng)文件上傳對(duì)比:

  • 傳統(tǒng)文件上傳:每次只能存/取一個(gè)文件,想要獲取其他的文件,則需重新打開;
  • showdirectorypicker:可以獲得整個(gè)訪問權(quán)限,可自由查看,可一次授權(quán)多測(cè)使用。

(1)基本用法

簡(jiǎn)單調(diào)用:

參數(shù)配置:

(2)核心對(duì)象和方法

權(quán)限系統(tǒng):

注:一般在用戶交互后使用,不在頁面加載時(shí)立即請(qǐng)求

(3)實(shí)際應(yīng)用

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} 個(gè)圖片文件`);
  }
  
  // 批量上傳
  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

是瀏覽器提供的用于在拖放操作中傳輸數(shù)據(jù)(例如一個(gè)快遞要從a處到b處,datatransfer對(duì)象就是快遞包裹;拖放事件就相當(dāng)于寄件,運(yùn)輸,收件的過程;拖放數(shù)據(jù)就相當(dāng)于包裹中的物品)。

(1)對(duì)象詳解

dropeffect:拖放操作(復(fù)制,移動(dòng),鏈接);

effectallowed:允許拖放的操作有哪些(權(quán)限設(shè)置)。

拖放的數(shù)據(jù)類型:

(2)基本使用

最簡(jiǎn)單的文本拖放

<!-- HTML結(jié)構(gòu) -->
<div id="source" draggable="true">拖拽我</div>
<div id="target">放到這里</div>

<script>
// JavaScript實(shí)現(xiàn)
const source = document.getElementById('source');
const target = document.getElementById('target');

// 1. 拖拽開始:準(zhǔn)備數(shù)據(jù)
source.addEventListener('dragstart', (event) => {
  console.log('開始拖拽');
  
  // 設(shè)置要傳輸?shù)臄?shù)據(jù)
  event.dataTransfer.setData('text/plain', '這是傳輸?shù)臄?shù)據(jù)');
  
  // 設(shè)置拖拽效果
  event.dataTransfer.effectAllowed = 'copy';
});

// 2. 拖拽到目標(biāo)上方:允許放置
target.addEventListener('dragover', (event) => {
  event.preventDefault(); // 必須調(diào)用,允許放置
  event.dataTransfer.dropEffect = 'copy';
  target.style.backgroundColor = '#e3f2fd';
});

// 3. 離開目標(biāo)區(qū)域
target.addEventListener('dragleave', () => {
  target.style.backgroundColor = '';
});

// 4. 放置數(shù)據(jù)
target.addEventListener('drop', (event) => {
  event.preventDefault();
  
  // 獲取傳輸?shù)臄?shù)據(jù)
  const data = event.dataTransfer.getData('text/plain');
  target.textContent = `接收到: ${data}`;
  target.style.backgroundColor = '';
  
  console.log('數(shù)據(jù)放置完成:', data);
});
</script>

(3)文件拖放

<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');

// 阻止瀏覽器默認(rèn)行為(打開文件)
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('拖放的文件數(shù)量:', files.length);
  
  // 處理每個(gè)文件
  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>
      修改時(shí)間: ${new Date(file.lastModified).toLocaleString()}
      <hr>
    `;
    fileList.appendChild(fileInfo);
    
    // 如果是圖片,創(chuàng)建預(yù)覽
    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()

是一個(gè)瀏覽器api,它能將內(nèi)存中的Blob,F(xiàn)ile,MediaSource對(duì)象轉(zhuǎn)換成一個(gè)臨時(shí)URL地址。

特點(diǎn):

  • 臨時(shí)性:只在當(dāng)前會(huì)話有效;
  • 每個(gè)對(duì)象生成唯一的URL;
  • 需要手動(dòng)釋放。

(1)基本語法

//簡(jiǎn)單示例
// 示例1:創(chuàng)建文本文件的下載鏈接
function createTextFileDownload() {
  // 1. 創(chuàng)建Blob對(duì)象
  const text = 'Hello, World! 這是要下載的內(nèi)容。';
  const blob = new Blob([text], { type: 'text/plain;charset=utf-8' });
  
  // 2. 創(chuàng)建Object URL
  const url = URL.createObjectURL(blob);
  console.log('生成的URL:', url);
  // 輸出: blob:http://localhost:3000/123e4567-e89b-12d3-a456-426614174000
  
  // 3. 使用URL創(chuàng)建下載鏈接
  const a = document.createElement('a');
  a.href = url;
  a.download = 'example.txt'; // 下載文件名
  a.textContent = '點(diǎn)擊下載文本文件';
  
  // 4. 添加到頁面并自動(dòng)點(diǎn)擊
  document.body.appendChild(a);
  a.click();
  
  // 5. 清理(重要?。?
  setTimeout(() => {
    URL.revokeObjectURL(url);
    document.body.removeChild(a);
    console.log('URL已回收,內(nèi)存已釋放');
  }, 100);
}

// 示例2:預(yù)覽圖片
function previewImage(file) {
  // file是用戶通過<input type="file">選擇的File對(duì)象
  
  // 1. 創(chuàng)建Object URL
  const imageUrl = URL.createObjectURL(file);
  
  // 2. 創(chuàng)建圖片元素并顯示
  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 = ''; // 清空之前的預(yù)覽
  container.appendChild(img);
  
  // 4. 圖片加載完成后清理(可選,看需求)
  img.onload = () => {
    console.log('圖片預(yù)覽已顯示,URL:', imageUrl);
    // 注意:這里不立即revoke,因?yàn)閳D片還在顯示
  };
  
  // 返回URL以便后續(xù)清理
  return imageUrl;
}

(2)工作原理

(3)與Data URL對(duì)比

// 對(duì)比示例:同一個(gè)圖片的兩種不同表示

// Data URL方式(數(shù)據(jù)在URL中)
const dataUrl = `data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUA
AAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO
9TXL0Y4OHwAAAABJRU5ErkJggg==`;

// Blob URL方式(數(shù)據(jù)在內(nèi)存中,URL是引用)
const blob = base64ToBlob(base64Data, 'image/png');
const blobUrl = URL.createObjectURL(blob);

// 比較表
const comparison = {
  DataURL: {
    存儲(chǔ)方式: '數(shù)據(jù)內(nèi)嵌在URL字符串中',
    長(zhǎng)度: '很長(zhǎng)(數(shù)據(jù)越大URL越長(zhǎng))',
    內(nèi)存: '每個(gè)使用處都有一份完整副本',
    更新: '需要重新生成整個(gè)URL',
    適用: '小于10KB的小資源'
  },
  BlobURL: {
    存儲(chǔ)方式: '數(shù)據(jù)在內(nèi)存中,URL是引用',
    長(zhǎng)度: '固定長(zhǎng)度,與數(shù)據(jù)大小無關(guān)',
    內(nèi)存: '數(shù)據(jù)只有一份,多處引用',
    更新: '更新Blob內(nèi)容,URL不變',
    適用: '任意大小的資源'
  }
};

(4)實(shí)際應(yīng)用

文件預(yù)覽系統(tǒng)

class FilePreviewSystem {
  constructor() {
    this.activeUrls = new Set(); // 跟蹤活躍的URL
    this.previewHistory = []; // 預(yù)覽歷史記錄
  }
  
  // 預(yù)覽單個(gè)文件
  previewFile(file) {
    if (!file) return null;
    
    // 清理之前的預(yù)覽(如果需要)
    this.cleanupPreviousPreviews();
    
    // 創(chuàng)建Object URL
    const url = URL.createObjectURL(file);
    this.activeUrls.add(url);
    
    // 根據(jù)文件類型選擇預(yù)覽方式
    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;
  }
  
  // 預(yù)覽圖片
  previewImage(url, fileName) {
    const container = document.getElementById('preview-container');
    
    // 創(chuàng)建圖片元素
    const img = document.createElement('img');
    img.src = url;
    img.alt = fileName;
    img.className = 'preview-image';
    
    // 添加加載和錯(cuò)誤處理
    img.onload = () => {
      console.log(`圖片加載成功: ${fileName}`);
      this.adjustPreviewSize(img);
    };
    
    img.onerror = () => {
      console.error(`圖片加載失敗: ${fileName}`);
      this.showErrorMessage('圖片加載失敗');
    };
    
    container.innerHTML = '';
    container.appendChild(img);
    
    // 添加清理回調(diào)
    img.addEventListener('unload', () => {
      this.revokeUrl(url);
    });
  }
  
  // 預(yù)覽視頻
  previewVideo(url, fileName) {
    const container = document.getElementById('preview-container');
    
    const video = document.createElement('video');
    video.src = url;
    video.controls = true;
    video.className = 'preview-video';
    
    // 添加元數(shù)據(jù)
    const meta = document.createElement('div');
    meta.className = 'video-meta';
    meta.textContent = `視頻: ${fileName}`;
    
    container.innerHTML = '';
    container.appendChild(video);
    container.appendChild(meta);
  }
  
  // 批量預(yù)覽文件
  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;
        
        // 點(diǎn)擊查看大圖
        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() {
    // 可以根據(jù)策略決定是否立即清理
    // 這里清理除當(dāng)前活躍外的所有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}`);
    }
  }
  
  // 頁面卸載時(shí)清理所有資源
  setupCleanupOnUnload() {
    window.addEventListener('beforeunload', () => {
      this.cleanupAllResources();
    });
    
    // 防止內(nèi)存泄漏
    window.addEventListener('pagehide', () => {
      this.cleanupAllResources();
    });
  }
  
  cleanupAllResources() {
    console.log('清理所有Blob URL資源');
    this.activeUrls.forEach(url => {
      try {
        URL.revokeObjectURL(url);
      } catch (e) {
        console.warn('清理URL時(shí)出錯(cuò):', e);
      }
    });
    this.activeUrls.clear();
    this.previewHistory = [];
  }
}

9、Fetch API

是一個(gè)基于promise的JavaScript API,用于發(fā)起網(wǎng)絡(luò)請(qǐng)求,代替了傳統(tǒng)的XMLHttpRequest。

(1)基本用法

a、簡(jiǎn)單的GET請(qǐng)求

// 基礎(chǔ)GET請(qǐng)求
fetch('https://api.example.com/data')
  .then(response => {
    if (!response.ok) {
      throw new Error('網(wǎng)絡(luò)響應(yīng)不正常');
    }
    return response.json(); // 解析JSON響應(yīng)
  })
  .then(data => {
    console.log('獲取到的數(shù)據(jù):', data);
    // 處理數(shù)據(jù)
  })
  .catch(error => {
    console.error('請(qǐng)求失敗:', error);
    // 錯(cuò)誤處理
  });

// 實(shí)際示例:獲取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錯(cuò)誤: ${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配置選項(xiàng)

// Fetch的完整配置對(duì)象
const fetchOptions = {
  // 請(qǐng)求方法
  method: 'GET', // GET, POST, PUT, DELETE, PATCH, HEAD, OPTIONS
  
  // 請(qǐng)求頭
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer your-token-here',
    'User-Agent': 'MyApp/1.0',
    'Accept': 'application/json'
  },
  
  // 請(qǐng)求體(GET和HEAD請(qǐng)求不能有body)
  body: JSON.stringify({ key: 'value' }),
  
  // 請(qǐng)求模式
  mode: 'cors', // cors, no-cors, same-origin
  
  // 緩存策略
  cache: 'default', // default, no-store, reload, no-cache, force-cache, only-if-cached
  
  // 是否發(fā)送憑證(cookies)
  credentials: 'same-origin', // omit, same-origin, include
  
  // 重定向策略
  redirect: 'follow', // follow, error, manual
  
  // 引用策略
  referrerPolicy: 'no-referrer-when-downgrade',
  
  // 完整性校驗(yàn)
  integrity: 'sha256-BpfBw7ivV8q2jLiT13fxDYAe2tJllusRSZ273h2nFSE=',
  
  // 請(qǐng)求超時(shí)(注意:Fetch原生不支持timeout,需要AbortController)
  signal: abortController.signal,
  
  // 優(yōu)先級(jí)
  priority: 'auto' // high, low, auto
};

c、HTTP方法示例

class ApiClient {
  // GET請(qǐng)求:獲取數(shù)據(jù)
  async getData(url, queryParams = {}) {
    // 構(gòu)建查詢字符串
    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請(qǐng)求:創(chuàng)建資源
  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請(qǐng)求:更新整個(gè)資源
  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請(qǐng)求:部分更新
  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請(qǐng)求:刪除資源
  async deleteData(url) {
    const response = await fetch(url, {
      method: 'DELETE'
    });
    
    return this.handleResponse(response, false); // 可能沒有響應(yīng)體
  }
  
  // 處理響應(yīng)
  async handleResponse(response, expectJson = true) {
    if (!response.ok) {
      const errorText = await response.text();
      throw new Error(`請(qǐng)求失敗: ${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));

// 創(chuàng)建新用戶
api.createData('/api/users', {
  name: '張三',
  email: 'zhangsan@example.com',
  age: 25
}).then(newUser => console.log('創(chuàng)建的用戶:', newUser));

(2)Response對(duì)象詳情

response對(duì)象的屬性和方法

// 響應(yīng)對(duì)象的結(jié)構(gòu)
const response = {
  // 屬性
  ok: true,                    // HTTP狀態(tài)碼在200-299范圍內(nèi)為true
  status: 200,                 // HTTP狀態(tài)碼
  statusText: 'OK',           // 狀態(tài)文本
  url: 'https://api.example.com/data', // 請(qǐng)求的URL
  redirected: false,          // 是否被重定向
  type: 'cors',              // basic, cors, error, opaque, opaqueredirect
  
  // Headers對(duì)象
  headers: Headers {},        // 響應(yīng)頭
  
  // 讀取響應(yīng)體的方法
  arrayBuffer(): Promise<ArrayBuffer>,
  blob(): Promise<Blob>,
  formData(): Promise<FormData>,
  json(): Promise<any>,
  text(): Promise<string>,
  
  // 克隆響應(yīng)
  clone(): Response,
  
  // 其他
  body: ReadableStream,       // 響應(yīng)體流
  bodyUsed: false            // 是否已讀取過響應(yīng)體
};

// 實(shí)際使用示例
async function processResponse(url) {
  const response = await fetch(url);
  
  console.log('狀態(tài)碼:', response.status);
  console.log('狀態(tài)文本:', response.statusText);
  console.log('請(qǐng)求成功?', response.ok);
  console.log('請(qǐng)求URL:', response.url);
  console.log('是否重定向?', response.redirected);
  console.log('響應(yīng)類型:', response.type);
  
  // 檢查響應(yīng)頭
  console.log('內(nèi)容類型:', response.headers.get('Content-Type'));
  console.log('內(nèi)容長(zhǎng)度:', response.headers.get('Content-Length'));
  
  // 遍歷所有響應(yīng)頭
  response.headers.forEach((value, key) => {
    console.log(`${key}: ${value}`);
  });
  
  // 根據(jù)內(nèi)容類型選擇解析方式
  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)實(shí)際應(yīng)用

a、上傳文件

class FileUploader {
  constructor() {
    this.chunkSize = 1024 * 1024; // 1MB分片
  }
  
  // 1. 簡(jiǎn)單文件上傳
  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時(shí)不要設(shè)置Content-Type頭,瀏覽器會(huì)自動(dòng)設(shè)置
    });
    
    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++;
        
        // 更新進(jìn)度
        if (onProgress) {
          const progress = (uploadedChunks / totalChunks) * 100;
          onProgress(progress, chunkIndex, totalChunks);
        }
        
        console.log(`分片 ${chunkIndex + 1}/${totalChunks} 上傳成功`);
        
      } catch (error) {
        console.error(`分片 ${chunkIndex} 上傳失敗:`, error);
        // 可以選擇重試或放棄
        throw error;
      }
    }
    
    // 通知服務(wù)器合并分片
    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. 帶進(jìn)度顯示的上傳
  async uploadWithProgress(file, uploadUrl) {
    const xhr = new XMLHttpRequest(); // 注意:這里用XHR為了進(jìn)度事件
    
    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(`上傳進(jìn)度: ${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('網(wǎng)絡(luò)錯(cuò)誤'));
      });
      
      xhr.addEventListener('abort', () => {
        reject(new Error('上傳被取消'));
      });
      
      xhr.open('POST', uploadUrl);
      xhr.send(formData);
    });
  }
}

// 使用示例
const uploader = new FileUploader();

// 簡(jiǎn)單上傳
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、請(qǐng)求超時(shí)控制

// Fetch原生不支持timeout,需要使用AbortController
class FetchWithTimeout {
  constructor(defaultTimeout = 10000) {
    this.defaultTimeout = defaultTimeout;
  }
  
  // 帶超時(shí)的fetch
  async fetchWithTimeout(url, options = {}) {
    const { timeout = this.defaultTimeout, ...fetchOptions } = options;
    
    // 創(chuàng)建AbortController
    const controller = new AbortController();
    const { signal } = controller;
    
    // 設(shè)置超時(shí)定時(shí)器
    const timeoutId = setTimeout(() => {
      controller.abort();
      console.warn(`請(qǐng)求超時(shí): ${url}, 超時(shí)時(shí)間: ${timeout}ms`);
    }, timeout);
    
    try {
      const response = await fetch(url, {
        ...fetchOptions,
        signal
      });
      
      clearTimeout(timeoutId);
      
      if (!response.ok) {
        throw new Error(`HTTP錯(cuò)誤: ${response.status}`);
      }
      
      return response;
    } catch (error) {
      clearTimeout(timeoutId);
      
      if (error.name === 'AbortError') {
        throw new Error(`請(qǐng)求超時(shí) (${timeout}ms)`);
      }
      
      throw error;
    }
  }
  
  // 使用示例
  async getDataWithTimeout() {
    try {
      const response = await this.fetchWithTimeout('https://api.example.com/slow-data', {
        timeout: 5000, // 5秒超時(shí)
        headers: {
          'Accept': 'application/json'
        }
      });
      
      return await response.json();
    } catch (error) {
      console.error('請(qǐng)求失敗:', error.message);
      return null;
    }
  }
}

// 批量請(qǐng)求,單個(gè)失敗不影響其他
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) {
      // 填充并發(fā)請(qǐng)求
      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);
        });
      }
      
      // 等待至少一個(gè)請(qǐng)求完成
      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' ? '請(qǐng)求超時(shí)' : 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}`);
});

總結(jié) 

到此這篇關(guān)于JavaScript文件的操作方法總結(jié)大全的文章就介紹到這了,更多相關(guān)JS文件操作方法內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

最新評(píng)論

淅川县| 桑植县| 孟津县| 慈溪市| 大田县| 合阳县| 饶河县| 响水县| 永泰县| 班玛县| 乐亭县| 阳高县| 巨鹿县| 班戈县| 泗水县| 鲁山县| 津市市| 闽侯县| 社旗县| 肇庆市| 清远市| 大安市| 仲巴县| 西充县| 资中县| 磐石市| 宝鸡市| 潼南县| 武山县| 公主岭市| 晋城| 潼南县| 上饶县| 黄浦区| 增城市| 峨山| 巢湖市| 长白| 克什克腾旗| 杭锦旗| 泰宁县|