文件处理 #
文件处理概述 #
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