文件处理 #

文件处理概述 #

JavaScript 提供了多种 API 用于处理文件,包括 File、Blob、FileReader 等。

text
┌─────────────────────────────────────────────────────────────┐
│                    文件处理 API                              │
├─────────────────────────────────────────────────────────────┤
│  ┌─────────────┐  ┌─────────────┐  ┌─────────────┐         │
│  │   File      │  │   Blob      │  │ FileReader  │         │
│  ├─────────────┤  ├─────────────┤  ├─────────────┤         │
│  │ 文件对象     │  │ 二进制数据   │  │ 文件读取     │         │
│  │ input.files │  │ Blob()      │  │ readAsText  │         │
│  │ 拖拽文件     │  │ 类型转换     │  │ readAsDataURL│        │
│  └─────────────┘  └─────────────┘  └─────────────┘         │
│                                                              │
│  ┌─────────────┐  ┌─────────────┐  ┌─────────────┐         │
│  │   URL       │  │  FormData   │  │  Canvas     │         │
│  ├─────────────┤  ├─────────────┤  ├─────────────┤         │
│  │ createObject│  │ 文件上传     │  │ 图片处理     │         │
│  │ URL         │  │ append()    │  │ toDataURL   │         │
│  └─────────────┘  └─────────────┘  └─────────────┘         │
└─────────────────────────────────────────────────────────────┘

基础函数 #

formatFileSize(size) #

格式化文件大小,将字节数转换为易读的格式。

javascript
const sizeUnit = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];

export const formatFileSize = (size) => {
    let filesize = size;
    let i = 0;
    sizeUnit.some((item, index) => {
        if (filesize < 1024) {
            i = index;
            return true;
        }
        filesize = filesize / 1024;
        return false;
    });
    return `${filesize.toFixed(2)}${sizeUnit[i]}`;
};

使用示例 #

javascript
import { formatFileSize } from './utils';

console.log(formatFileSize(500));
console.log(formatFileSize(1024));
console.log(formatFileSize(1024 * 1024));
console.log(formatFileSize(1024 * 1024 * 1024));
console.log(formatFileSize(1024 * 1024 * 1024 * 1024));

downloadFile(url, filename) #

触发文件下载。

javascript
export const downloadFile = (url, filename) => {
    const a = document.createElement('a');
    a.href = url;
    a.download = filename;
    a.click();
};

使用示例 #

javascript
import { downloadFile } from './utils';

downloadFile('https://example.com/file.pdf', 'document.pdf');
downloadFile('https://example.com/image.png', 'photo.png');

getFileUrl(file) #

获取本地文件的预览 URL。

javascript
export const getFileUrl = (file) => {
    let url = null;
    if (window.createObjectURL !== undefined) {
        url = window.createObjectURL(file);
    } else if (window.URL !== undefined) {
        url = window.URL.createObjectURL(file);
    } else if (window.webkitURL !== undefined) {
        url = window.webkitURL.createObjectURL(file);
    }
    return url;
};

使用示例 #

javascript
import { getFileUrl } from './utils';

const input = document.querySelector('input[type="file"]');
input.addEventListener('change', (e) => {
    const file = e.target.files[0];
    const url = getFileUrl(file);
    
    const img = document.createElement('img');
    img.src = url;
    document.body.appendChild(img);
    
    URL.revokeObjectURL(url);
});

readFileAsText(file) #

读取文件内容为文本。

javascript
export const readFileAsText = (file) => {
    return new Promise((resolve, reject) => {
        const reader = new FileReader();
        reader.onload = e => resolve(e.target.result);
        reader.onerror = e => reject(e);
        reader.readAsText(file, 'UTF-8');
    });
};

使用示例 #

javascript
import { readFileAsText } from './utils';

const input = document.querySelector('input[type="file"]');
input.addEventListener('change', async (e) => {
    const file = e.target.files[0];
    const content = await readFileAsText(file);
    console.log(content);
});

进阶用法 #

getImageBase64(file, targetType) #

将图片文件转换为 Base64 编码。

javascript
export const getImageBase64 = (file, targetType) => {
    targetType = targetType || file.type;
    return new Promise((resolve, reject) => {
        if (file.type === 'image/svg+xml') {
            const reader = new FileReader();
            reader.onload = (e) => {
                const svgContent = e.target.result;
                const img = new Image();
                img.onload = () => {
                    const canvas = document.createElement('canvas');
                    canvas.width = img.width;
                    canvas.height = img.height;
                    const ctx = canvas.getContext('2d');
                    ctx.imageSmoothingEnabled = true;
                    ctx.imageSmoothingQuality = 'high';
                    ctx.drawImage(img, 0, 0, img.width, img.height);
                    const dataURL = canvas.toDataURL(targetType, 1.0);
                    resolve(dataURL);
                };
                img.onerror = (err) => reject(err);
                img.src = 'data:image/svg+xml;charset=utf-8,' + encodeURIComponent(svgContent);
            };
            reader.onerror = (err) => reject(err);
            reader.readAsText(file);
        } else {
            const url = getFileUrl(file);
            const img = new Image();
            img.setAttribute('crossOrigin', 'anonymous');
            img.src = url;
            img.onload = () => {
                const canvas = document.createElement('canvas');
                canvas.width = img.width;
                canvas.height = img.height;
                const ctx = canvas.getContext('2d');
                ctx.drawImage(img, 0, 0);
                const dataURL = canvas.toDataURL(targetType);
                resolve(dataURL);
            };
            img.onerror = (err) => reject(err);
        }
    });
};

使用示例 #

javascript
import { getImageBase64 } from './utils';

const input = document.querySelector('input[type="file"]');
input.addEventListener('change', async (e) => {
    const file = e.target.files[0];
    const base64 = await getImageBase64(file);
    console.log(base64);
    
    const base64Png = await getImageBase64(file, 'image/png');
    console.log(base64Png);
});

文件上传 #

javascript
const uploadFile = async (url, file, options = {}) => {
    const {
        fieldName = 'file',
        data = {},
        onProgress,
        headers = {}
    } = options;
    
    return new Promise((resolve, reject) => {
        const xhr = new XMLHttpRequest();
        const formData = new FormData();
        
        formData.append(fieldName, file);
        Object.entries(data).forEach(([key, value]) => {
            formData.append(key, value);
        });
        
        xhr.upload.addEventListener('progress', (e) => {
            if (e.lengthComputable && onProgress) {
                const percent = Math.round((e.loaded / e.total) * 100);
                onProgress(percent);
            }
        });
        
        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.open('POST', url);
        Object.entries(headers).forEach(([key, value]) => {
            xhr.setRequestHeader(key, value);
        });
        xhr.send(formData);
    });
};

await uploadFile('/api/upload', file, {
    fieldName: 'avatar',
    data: { userId: 123 },
    onProgress: (percent) => console.log(`上传进度: ${percent}%`)
});

文件分片上传 #

javascript
const uploadChunk = async (url, chunk, chunkIndex, totalChunks, fileId) => {
    const formData = new FormData();
    formData.append('chunk', chunk);
    formData.append('chunkIndex', chunkIndex);
    formData.append('totalChunks', totalChunks);
    formData.append('fileId', fileId);
    
    const response = await fetch(url, {
        method: 'POST',
        body: formData
    });
    return response.json();
};

const uploadLargeFile = async (url, file, chunkSize = 5 * 1024 * 1024) => {
    const totalChunks = Math.ceil(file.size / chunkSize);
    const fileId = `${Date.now()}-${file.name}`;
    
    for (let i = 0; i < totalChunks; i++) {
        const start = i * chunkSize;
        const end = Math.min(start + chunkSize, file.size);
        const chunk = file.slice(start, end);
        
        console.log(`上传分片 ${i + 1}/${totalChunks}`);
        await uploadChunk(url, chunk, i, totalChunks, fileId);
    }
    
    console.log('所有分片上传完成');
};

await uploadLargeFile('/api/upload/chunk', largeFile);

文件下载(带进度) #

javascript
const downloadWithProgress = async (url, onProgress) => {
    const response = await fetch(url);
    const reader = response.body.getReader();
    const contentLength = +response.headers.get('Content-Length');
    
    let receivedLength = 0;
    const chunks = [];
    
    while (true) {
        const { done, value } = await reader.read();
        if (done) break;
        
        chunks.push(value);
        receivedLength += value.length;
        
        if (onProgress) {
            const percent = Math.round((receivedLength / contentLength) * 100);
            onProgress(percent, receivedLength, contentLength);
        }
    }
    
    const blob = new Blob(chunks);
    return blob;
};

const blob = await downloadWithProgress(
    'https://example.com/large-file.zip',
    (percent, loaded, total) => {
        console.log(`下载进度: ${percent}%`);
    }
);
downloadFile(URL.createObjectURL(blob), 'large-file.zip');

实际应用场景 #

1. 图片预览 #

javascript
import { getFileUrl, getImageBase64 } from './utils';

class ImagePreview {
    constructor(inputElement, previewElement) {
        this.input = inputElement;
        this.preview = previewElement;
        this.bindEvents();
    }
    
    bindEvents() {
        this.input.addEventListener('change', (e) => {
            const files = e.target.files;
            this.preview.innerHTML = '';
            
            Array.from(files).forEach(file => {
                if (!file.type.startsWith('image/')) return;
                
                const url = getFileUrl(file);
                const img = document.createElement('img');
                img.src = url;
                img.style.maxWidth = '200px';
                img.style.margin = '10px';
                this.preview.appendChild(img);
            });
        });
    }
    
    async getBase64Images() {
        const files = this.input.files;
        const results = [];
        
        for (const file of files) {
            if (file.type.startsWith('image/')) {
                const base64 = await getImageBase64(file);
                results.push({ name: file.name, base64 });
            }
        }
        
        return results;
    }
}

2. 文件拖拽上传 #

javascript
class DropZone {
    constructor(element, options = {}) {
        this.element = element;
        this.options = options;
        this.bindEvents();
    }
    
    bindEvents() {
        this.element.addEventListener('dragover', (e) => {
            e.preventDefault();
            this.element.classList.add('drag-over');
        });
        
        this.element.addEventListener('dragleave', () => {
            this.element.classList.remove('drag-over');
        });
        
        this.element.addEventListener('drop', async (e) => {
            e.preventDefault();
            this.element.classList.remove('drag-over');
            
            const files = e.dataTransfer.files;
            await this.handleFiles(files);
        });
    }
    
    async handleFiles(files) {
        for (const file of files) {
            if (this.options.onFile) {
                await this.options.onFile(file);
            }
        }
    }
}

const dropZone = new DropZone(document.getElementById('drop-area'), {
    onFile: async (file) => {
        console.log('上传文件:', file.name);
    }
});

3. Excel 导出 #

javascript
const exportToCSV = (data, filename) => {
    const headers = Object.keys(data[0]);
    const csvContent = [
        headers.join(','),
        ...data.map(row => headers.map(h => `"${row[h] || ''}"`).join(','))
    ].join('\n');
    
    const blob = new Blob(['\ufeff' + csvContent], { type: 'text/csv;charset=utf-8;' });
    downloadFile(URL.createObjectURL(blob), filename);
};

const exportToJSON = (data, filename) => {
    const jsonContent = JSON.stringify(data, null, 2);
    const blob = new Blob([jsonContent], { type: 'application/json' });
    downloadFile(URL.createObjectURL(blob), filename);
};

const users = [
    { id: 1, name: 'John', email: 'john@example.com' },
    { id: 2, name: 'Jane', email: 'jane@example.com' }
];

exportToCSV(users, 'users.csv');
exportToJSON(users, 'users.json');

4. 文件类型检测 #

javascript
const getFileExtension = (filename) => {
    return filename.slice((filename.lastIndexOf('.') - 1 >>> 0) + 2);
};

const getFileType = (file) => {
    const extension = getFileExtension(file.name).toLowerCase();
    const typeMap = {
        image: ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp', 'svg'],
        video: ['mp4', 'avi', 'mov', 'wmv', 'flv', 'mkv'],
        audio: ['mp3', 'wav', 'ogg', 'flac', 'aac'],
        document: ['pdf', 'doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx'],
        archive: ['zip', 'rar', '7z', 'tar', 'gz'],
        code: ['js', 'ts', 'py', 'java', 'cpp', 'html', 'css']
    };
    
    for (const [type, extensions] of Object.entries(typeMap)) {
        if (extensions.includes(extension)) {
            return type;
        }
    }
    return 'other';
};

const validateFileType = (file, allowedTypes) => {
    const fileType = getFileType(file);
    return allowedTypes.includes(fileType);
};

console.log(getFileType({ name: 'photo.jpg' }));
console.log(validateFileType({ name: 'document.pdf' }, ['document', 'image']));

下一步 #

现在你已经掌握了文件处理,接下来学习 颜色转换,了解 RGB 和 HEX 颜色格式的转换!

最后更新:2026-04-04