面試常考--前端性能優化之大文件上傳
大文件上傳是前端開發中常見的需求之一,特別是在需要處理高清圖片、視頻或其他大型文件時。優化大文件上傳不僅可以提升用戶體驗,還能有效減輕服務器負擔。本文將深入探討大文件上傳的幾種常見優化技術,包括文件切片與并發上傳、斷點續傳、后臺處理優化、安全性考慮和用戶體驗優化。
1. 前言
在現代Web應用中,用戶上傳大文件已成為常見需求。然而,直接上傳大文件會面臨諸多挑戰,例如網絡不穩定導致上傳中斷、長時間上傳導致用戶體驗差、服務器壓力大等。因此,優化大文件上傳性能顯得尤為重要。
2. 文件切片與并發上傳
2.1 文件切片原理
文件切片(Chunking)是將大文件分成若干小片段,每個片段獨立上傳的方法。這樣做可以有效減少單次上傳的數據量,降低上傳失敗的概率。
2.2 實現步驟
- 前端切片:利用
Blob對象的slice方法將文件切片。 - 并發上傳:使用
Promise.all實現多個切片并發上傳。 - 合并請求:上傳完成后,通知服務器合并這些切片。
3. 斷點續傳
斷點續傳(Resumable Uploads)可以在上傳過程中斷時,從斷點繼續上傳,避免重新上傳整個文件。
3.1 實現步驟
- 前端記錄進度:使用
localStorage記錄已上傳的切片信息。 - 斷點續傳:上傳時檢查哪些切片未上傳,繼續上傳未完成的部分。
4. 后臺處理優化
4.1 分片接收與合并
服務器需要支持接收分片請求,并在所有分片上傳完成后合并文件。可以利用中間件或服務端程序語言實現這一邏輯。
5. 安全性考慮
5.1 文件類型校驗
在前端和后端都應對文件類型進行校驗,確保上傳的文件類型符合預期。
5.2 文件大小限制
限制單個文件和總上傳文件的大小,防止惡意用戶上傳過大的文件造成服務器壓力。
6. 用戶體驗優化
6.1 進度顯示
通過顯示上傳進度條,讓用戶了解上傳進度,提升用戶體驗。
6.2 網絡波動處理
考慮到用戶可能在網絡不穩定的環境中上傳文件,可以增加失敗重試機制。
完整實例
后端代碼(Node.js + Express)
安裝依賴
npm init -y
npm install express multer fs
創建服務器文件(server.js)
const express = require('express'); const multer = require('multer'); const fs = require('fs'); const path = require('path'); const bodyParser = require('body-parser'); const app = express(); const upload = multer({ dest: 'uploads/' }); app.use(bodyParser.json()); // 路由:處理文件切片上傳 app.post('/upload', upload.single('chunk'), (req, res) => { const { index, fileName } = req.body; const chunkPath = path.join(__dirname, 'uploads', `${fileName}-${index}`); fs.renameSync(req.file.path, chunkPath); res.status(200).send('Chunk uploaded'); }); // 路由:合并切片 app.post('/merge', (req, res) => { const { totalChunks, fileName } = req.body; const filePath = path.join(__dirname, 'uploads', fileName); const writeStream = fs.createWriteStream(filePath); for (let i = 0; i < totalChunks; i++) { const chunkPath = path.join(__dirname, 'uploads', `${fileName}-${i}`); const data = fs.readFileSync(chunkPath); writeStream.write(data); fs.unlinkSync(chunkPath); } writeStream.end(); res.status(200).send('File merged'); }); app.listen(3000, () => { console.log('Server started on http://localhost:3000'); });
前端代碼(index.html + script.js)
- 創建HTML文件(index.html)
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>大文件上傳</title> </head> <body> <input type="file" id="fileInput"> <progress id="progressBar" value="0" max="100"></progress> <button onclick="uploadFile()">上傳文件</button> <script src="script.js"></script> </body> </html>
- 創建JavaScript文件(script.js)
const fileInput = document.getElementById('fileInput'); const progressBar = document.getElementById('progressBar'); const chunkSize = 5 * 1024 * 1024; // 5MB const uploadChunk = async (chunk, index, fileName) => { const formData = new FormData(); formData.append('chunk', chunk); formData.append('index', index); formData.append('fileName', fileName); await fetch('/upload', { method: 'POST', body: formData }); updateProgressBar(index); }; const updateProgressBar = (index) => { const uploadedChunks = JSON.parse(localStorage.getItem('uploadedChunks')) || []; if (!uploadedChunks.includes(index)) { uploadedChunks.push(index); progressBar.value = (uploadedChunks.length / totalChunks) * 100; localStorage.setItem('uploadedChunks', JSON.stringify(uploadedChunks)); } }; const uploadFile = async () => { const file = fileInput.files[0]; const totalChunks = Math.ceil(file.size / chunkSize); const uploadedChunks = JSON.parse(localStorage.getItem('uploadedChunks')) || []; const promises = []; for (let i = 0; i < totalChunks; i++) { if (!uploadedChunks.includes(i)) { const chunk = file.slice(i * chunkSize, (i + 1) * chunkSize); promises.push(uploadChunk(chunk, i, file.name)); } } await Promise.all(promises); await fetch('/merge', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ totalChunks, fileName: file.name }) }); localStorage.removeItem('uploadedChunks'); alert('文件上傳成功'); };
啟動后端服務器
- 在瀏覽器中打開前端頁面
將index.html文件在瀏覽器中打開,選擇文件并點擊“上傳文件”按鈕即可看到文件上傳進度。
node server.js


浙公網安備 33010602011771號