URL.createObjectURL()详解
基本概念
URL.createObjectURL()是一个静态方法,用于为 Blob 或 File 对象创建一个唯一的 URL。这个 URL 可以在浏览器中像普通 URL 一样使用,但指向的是内存中的对象。
基本语法
javascript
const objectURL = URL.createObjectURL(object);
参数:
object:File、Blob 或 MediaSource 对象返回值:一个字符串格式的 URL,格式为
blob:origin/uuid
示例:
javascript
// 为文本 Blob 创建 URL const textBlob = new Blob(['Hello, World!'], { type: 'text/plain' }); const blobURL = URL.createObjectURL(textBlob); console.log(blobURL); // blob:http://localhost:3000/550e8400-e29b-41d4-a716-446655440000主要用途
1. 预览本地图片
javascript
// 图片预览功能 const input = document.getElementById('image-input'); const preview = document.getElementById('preview'); input.addEventListener('change', (e) => { const file = e.target.files[0]; if (file) { // 创建对象 URL const imageURL = URL.createObjectURL(file); // 显示预览 preview.src = imageURL; preview.style.display = 'block'; // 清理之前的 URL(如果有) if (preview.dataset.url) { URL.revokeObjectURL(preview.dataset.url); } // 保存当前 URL 引用 preview.dataset.url = imageURL; } }); // 页面卸载时清理 window.addEventListener('beforeunload', () => { if (preview.dataset.url) { URL.revokeObjectURL(preview.dataset.url); } });2. 下载生成的内容
javascript
// 动态生成并下载文件 function downloadCSV(data, filename = 'data.csv') { // 创建 CSV 内容 const csvContent = data.map(row => row.map(cell => `"${cell}"`).join(',') ).join('\n'); // 创建 Blob const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' }); // 创建对象 URL const url = URL.createObjectURL(blob); // 创建下载链接 const link = document.createElement('a'); link.href = url; link.download = filename; link.style.display = 'none'; // 触发下载 document.body.appendChild(link); link.click(); document.body.removeChild(link); // 清理 URL setTimeout(() => URL.revokeObjectURL(url), 100); } // 使用示例 downloadCSV([ ['姓名', '年龄', '城市'], ['张三', '25', '北京'], ['李四', '30', '上海'] ]);3. 视频/音频播放
javascript
// 播放录制的音视频 let mediaRecorder; let recordedChunks = []; // 开始录制 async function startRecording() { const stream = await navigator.mediaDevices.getUserMedia({ video: true, audio: true }); mediaRecorder = new MediaRecorder(stream); mediaRecorder.ondataavailable = (event) => { if (event.data.size > 0) { recordedChunks.push(event.data); } }; mediaRecorder.start(); } // 停止并播放录制内容 function stopAndPlayRecording() { mediaRecorder.stop(); mediaRecorder.onstop = () => { // 创建包含录制数据的 Blob const blob = new Blob(recordedChunks, { type: 'video/webm' }); // 创建对象 URL const videoURL = URL.createObjectURL(blob); // 播放视频 const video = document.getElementById('player'); video.src = videoURL; video.controls = true; video.play(); // 清理旧的 URL if (video.dataset.url) { URL.revokeObjectURL(video.dataset.url); } video.dataset.url = videoURL; recordedChunks = []; }; }4. 创建 Web Worker
javascript
// 动态创建 Worker function createInlineWorker(workerScript) { const blob = new Blob([workerScript], { type: 'application/javascript' }); const workerURL = URL.createObjectURL(blob); const worker = new Worker(workerURL); // 立即释放 URL(Worker 已加载) URL.revokeObjectURL(workerURL); return worker; } // 使用 const worker = createInlineWorker(` self.onmessage = function(e) { const result = e.data * 2; postMessage(result); }; `); worker.onmessage = (e) => console.log('结果:', e.data); worker.postMessage(42); // 输出: 结果: 84技术细节
URL 的生命周期
javascript
const blob = new Blob(['data']); const url = URL.createObjectURL(blob); // 此时: // 1. 浏览器为这个 Blob 创建一个内部引用 // 2. 生成一个唯一的 URL // 3. 这个 URL 在文档卸载前有效,除非手动释放 // 使用后需要释放 URL.revokeObjectURL(url); // 释放后: // 1. URL 立即失效 // 2. 如果 Blob 没有其他引用,会被垃圾回收 // 3. 再次访问该 URL 会导致 404 错误性能优化技巧
批量处理文件预览
javascript
class ImagePreviewManager { constructor() { this.urls = new Set(); } createPreview(files) { // 清理之前的预览 this.cleanup(); const previews = []; for (const file of files) { if (file.type.startsWith('image/')) { const url = URL.createObjectURL(file); this.urls.add(url); previews.push({ file, url, element: this.createImageElement(url) }); } } return previews; } createImageElement(url) { const img = new Image(); img.src = url; return img; } cleanup() { // 释放所有 URL for (const url of this.urls) { URL.revokeObjectURL(url); } this.urls.clear(); } } // 使用 const manager = new ImagePreviewManager(); document.getElementById('upload').addEventListener('change', (e) => { const previews = manager.createPreview(Array.from(e.target.files)); // 显示预览... }); // 页面离开时清理 window.addEventListener('beforeunload', () => manager.cleanup());懒加载和缓存
javascript
const blobCache = new Map(); function getCachedBlobURL(content, type = 'text/plain') { const key = `${type}:${content}`; if (!blobCache.has(key)) { const blob = new Blob([content], { type }); const url = URL.createObjectURL(blob); blobCache.set(key, { url, timestamp: Date.now(), refCount: 0 }); // 定期清理缓存(30秒) setTimeout(() => { const cached = blobCache.get(key); if (cached && cached.refCount === 0) { URL.revokeObjectURL(cached.url); blobCache.delete(key); } }, 30000); } const cached = blobCache.get(key); cached.refCount++; return { url: cached.url, release: () => { cached.refCount--; } }; }常见问题与解决方案
问题1:内存泄漏
javascript
// 错误示例:每次都创建新 URL 但不释放 function createDownloadLink(content) { const blob = new Blob([content]); const url = URL.createObjectURL(blob); // 每次都创建 const link = document.createElement('a'); link.href = url; // 忘记释放 URL! } // 正确示例 function createDownloadLink(content) { const blob = new Blob([content]); const url = URL.createObjectURL(blob); const link = document.createElement('a'); link.href = url; link.download = 'file.txt'; // 点击后清理 link.onclick = () => { setTimeout(() => URL.revokeObjectURL(url), 100); }; return link; }问题2:URL 在 iframe 中的使用
javascript
// 在 iframe 中显示生成的 PDF function showPDFInIframe(pdfData) { const blob = new Blob([pdfData], { type: 'application/pdf' }); const url = URL.createObjectURL(blob); const iframe = document.createElement('iframe'); iframe.src = url; document.body.appendChild(iframe); // iframe 卸载时清理 iframe.addEventListener('load', () => { // PDF 已加载,可以释放 URL URL.revokeObjectURL(url); }); }问题3:与 Service Worker 配合
javascript
// Service Worker 中缓存 Blob // 主线程 const blob = new Blob([data]); const url = URL.createObjectURL(blob); fetch(url) .then(response => response.blob()) .then(blobData => { // 缓存到 Service Worker caches.open('my-cache').then(cache => { cache.put('/cached-data', new Response(blobData)); }); // 释放 URL URL.revokeObjectURL(url); });浏览器兼容性
支持情况:
Chrome 8+
Firefox 4+
Safari 6+
Edge 12+
IE 10+
特性检测:
javascript
if (window.URL && window.URL.createObjectURL) { // 支持 const url = URL.createObjectURL(blob); } else { // 降级方案:使用 FileReader const reader = new FileReader(); reader.onload = (e) => { const dataUrl = e.target.result; // data: URL // 使用 data URL }; reader.readAsDataURL(blob); }最佳实践总结
及时释放:使用后立即调用
revokeObjectURL()错误处理:在 try-catch-finally 中确保释放
引用计数:复杂场景使用引用计数管理
作用域控制:将 URL 限制在最小作用域内
监控工具:使用浏览器开发者工具监控内存使用
javascript
// 最佳实践封装 function withObjectURL(blob, callback) { const url = URL.createObjectURL(blob); try { return callback(url); } finally { // 确保无论成功失败都释放 URL.revokeObjectURL(url); } } // 使用 const result = withObjectURL( new Blob(['data']), (url) => { // 在这里使用 url const img = new Image(); img.src = url; return img; } );URL.createObjectURL()是现代 Web 开发中非常强大的工具,正确使用可以显著提升用户体验,但需要谨慎管理内存。
URL.revokeObjectURL()详解
基本概念
URL.revokeObjectURL()用于释放通过URL.createObjectURL()创建的对象 URL。这是内存管理的重要步骤。
基本用法
javascript
// 创建对象 URL const blob = new Blob(['Hello World'], { type: 'text/plain' }); const blobURL = URL.createObjectURL(blob); // 使用对象 URL const link = document.createElement('a'); link.href = blobURL; link.download = 'hello.txt'; // 使用完毕后释放 URL.revokeObjectURL(blobURL);在 Web Worker 中的应用
正确的工作流程:
javascript
// 1. 创建 Worker 脚本 const workerScript = `self.onmessage=({data})=>{console.log(data)};`; const workerScriptBlob = new Blob([workerScript]); const workerScriptBlobURL = URL.createObjectURL(workerScriptBlob); // 2. 创建 Worker const worker = new Worker(workerScriptBlobURL); // 3. Worker 创建后立即释放 URL(不会影响已创建的 Worker) URL.revokeObjectURL(workerScriptBlobURL); // 4. 使用 Worker worker.postMessage('Hello Worker'); // 5. 不再需要时终止 Worker worker.terminate();为什么需要revokeObjectURL()
1. 内存管理
javascript
// 如果不释放,每次调用都会创建新的 URL,导致内存泄漏 function createWorkerLeaky(script) { const blob = new Blob([script]); const url = URL.createObjectURL(blob); // 每次调用都创建新 URL const worker = new Worker(url); // 忘记释放:URL 会一直占用内存 return worker; } // 正确版本 function createWorker(script) { const blob = new Blob([script]); const url = URL.createObjectURL(blob); const worker = new Worker(url); URL.revokeObjectURL(url); // 立即释放 return worker; }2. 安全考虑
释放 URL 可以防止其他代码误用或访问已被删除的 Blob 内容。
关键特性
立即释放不影响已创建的对象
javascript
const blob = new Blob(['content']); const url = URL.createObjectURL(blob); // 创建 Worker 或下载链接 const worker = new Worker(url); const link = document.createElement('a'); link.href = url; // 立即释放 URL URL.revokeObjectURL(url); // 以下仍然有效: worker.postMessage('test'); // Worker 正常工作 link.click(); // 下载仍然进行 // 但这些会失败: const img = new Image(); img.src = url; // 错误:URL 已被撤销生命周期示例
javascript
class WorkerManager { constructor() { this.workers = new Map(); // 存储 worker 和对应的 URL } createWorker(script) { const blob = new Blob([script]); const url = URL.createObjectURL(blob); const worker = new Worker(url); // 存储引用以便后续清理 this.workers.set(worker, url); // 可以立即释放 URL URL.revokeObjectURL(url); return worker; } terminateWorker(worker) { if (this.workers.has(worker)) { worker.terminate(); this.workers.delete(worker); } } // 清理所有 Worker cleanup() { for (const [worker, url] of this.workers) { worker.terminate(); // URL 已经释放,无需再次调用 revokeObjectURL } this.workers.clear(); } }与 Data URL 的对比
Blob URL 方式(需要清理)
javascript
// 需要主动管理内存 const url = URL.createObjectURL(blob); // ... 使用 ... URL.revokeObjectURL(url); // 必须调用
Data URL 方式(自动管理)
javascript
// 自动垃圾回收,无需手动清理 const worker = new Worker(`data:application/javascript;base64,${btoa(script)}`); // 使用完毕后,GC 会自动回收常见问题与解决方案
问题1:过早释放
javascript
// 错误:释放过早 const url = URL.createObjectURL(blob); URL.revokeObjectURL(url); // 立即释放 const worker = new Worker(url); // 错误:URL 已失效 // 正确:创建后再释放 const url = URL.createObjectURL(blob); const worker = new Worker(url); URL.revokeObjectURL(url); // Worker 创建后释放
问题2:重复释放
javascript
// 重复调用是安全的 const url = URL.createObjectURL(blob); URL.revokeObjectURL(url); URL.revokeObjectURL(url); // 安全,不会报错
问题3:异步操作中的释放
javascript
// 在异步操作中正确管理 URL async function processWithWorker(script, data) { const blob = new Blob([script]); const url = URL.createObjectURL(blob); try { const worker = new Worker(url); // 立即释放 URL,不影响 Worker URL.revokeObjectURL(url); return new Promise((resolve, reject) => { worker.onmessage = (e) => { worker.terminate(); resolve(e.data); }; worker.onerror = reject; worker.postMessage(data); }); } catch (error) { // 发生错误时也要释放 URL.revokeObjectURL(url); throw error; } }最佳实践
立即释放原则:创建对象后立即释放 URL
错误处理:在 try-catch 中确保释放
避免全局变量:将 URL 存储在最小作用域中
使用封装函数:
javascript
function createWorkerWithCleanup(script) { const blob = new Blob([script], { type: 'application/javascript' }); const url = URL.createObjectURL(blob); let worker; try { worker = new Worker(url); } finally { // 无论如何都释放 URL URL.revokeObjectURL(url); } return worker; } // 或者使用 IIFE 封装 const worker = (() => { const blob = new Blob([script]); const url = URL.createObjectURL(blob); const w = new Worker(url); URL.revokeObjectURL(url); return w; })();浏览器兼容性
现代浏览器完全支持
IE 10+ 支持
移动端浏览器普遍支持
URL.revokeObjectURL()是 Web 开发中重要的内存管理工具,特别是在使用 Blob 和 File API 时。正确的使用可以避免内存泄漏,提升应用性能。