先记住这个答案
URL.createObjectURL 会让 Blob 进入浏览器的内部引用表,使其无法被垃圾回收,直到调用 revokeObjectURL 或当前文档卸载。泄漏通常来自频繁为图片、视频或文件分片创建 URL 却从不撤销,表现为内存占用单调上升、页面响应迟缓。正确做法是在资源不再需要时立即撤销,比如在 <img> 的 load 回调中完成解码后调用,确保峰值内存可控。
- URL 持有 Blob 引用直到显式 revoke
- 反复创建不撤销时内存占用持续升高
- 避免泄漏要在不再使用时立刻撤销
对象 URL 的内部引用机制
URL.createObjectURL(blob) 在生成以 blob: 开头的字符串后,浏览器会将这个字符串与 Blob 对象进行一对一登记,且登记是强引用。即使你随后不把字符串实际赋值给任何元素,甚至该字符串没有被任何变量保存,只要没有调用 URL.revokeObjectURL(url),底层 Blob 数据就会一直保留,垃圾回收无法回收它。要终止这条引用,标准途径是调用 URL.revokeObjectURL(url),它会把登记项从内部表移除。此操作与将 DOM 的 src 置空没有关系;浏览器不会统计某个 URL 被引用了几次,也不会在 <img> 或 <video> 不再使用它时自动执行 revoke。因此,决定何时释放完全依赖你的显式调用,遗漏一次就会让一个 Blob 存续到文档卸载。
要终止这条引用,标准途径是调用 URL.revokeObjectURL(url),它会把登记项从内部表移除。此操作与将 DOM 的 src 置空没有关系;浏览器不会统计某个 URL 被引用了几次,也不会在 <img> 或 <video> 不再使用它时自动执行 revoke。因此,决定何时释放完全依赖你的显式调用,遗漏一次就会让一个 Blob 存续到文档卸载。
图片预览反复选择的泄漏路径
某后台页面提供商品图片预览:用户通过 <input type=file> 选取图片,脚本用 createObjectURL 生成地址并赋给一个固定的 <img>。用户连续选择不同文件,每次控件触发 change 时生成新 URL,而旧 URL 从不撤销。假设每张原图约 3MB,选择 20 次后,内部至少滞留 60MB 原始数据,且不随图片标签更替而消失,内存监控持续上升,页面开始卡顿。
修复策略是把撤销放在每次选择前,或放在 img.onload 后。在 onload 时撤销最安全,因为图片已完成解码,其渲染数据已经转化为位图,不再依赖原始 Blob。于是代码可以在生成新 URL 前撤销上一个,或统一管理一组 URL,在替换时逐个 revoke。这样任意时刻只有一个有效 URL,峰值内存被控制在单张图片大小左右,页面恢复流畅。
泄漏影响的范围与处理边界
文档卸载会隐式清理所有对象 URL,所以在传统多页应用中如果刷新频繁,泄漏影响往往不明显。但在单页应用里,用户可能长时间不刷新,期间反复上传、预览或下载文件,未 revoke 的累积值就相当可观。另外,仅清空 src 属性或隐藏元素并不能解除内部登记,必须在移除 DOM 引用时一并执行撤销。
判断标准很简单:只要用 createObjectURL 生成过地址,就需要有对应的 revokeObjectURL 调用。如果代码里没有显式记录和管理这些 URL,就应引入一个负责统一分配与释放的小工具,保存到数组并在复用前批量撤销。对于大量 Blob 分片,尤其重要。若资源只短暂使用且每次操作伴随页面跳转,未撤销不一定酿成事故,但主动维护仍是更稳妥的工程选择。
容易答错的地方
- 置空 src 即可释放
- 把
img.src设为空字符串只会让该标签停止加载该图片,不会撤销内部登记。所以旧 Blob 仍被字符串引用,内存不会释放。要真正释放,必须获取当时生成的字符串并调用revokeObjectURL。 - 浏览器会自动回收未使用对象
- 认为 URL 没有赋给 DOM 后 Blob 能被自动回收。实际浏览器不会因字符串未挂载而清除内部登记,只有 revoke 调用或文档卸载才释放,所以应用代码要负责撤销。
面试官还会怎么问?
重复调用 createObjectURL 创建两个 URL 是否分别撤销?
每次创建都会产生独立 URL,且各有一条内部引用。要让 Blob 完全释放,需要分别调用 revokeObjectURL 对每个 URL 撤销。缺少任何一个,对应映射就保留下来消耗内存。
在 Web Worker 或 Service Worker 中能使用吗?
Web Worker 可以使用,但 URL 生命周期绑定到 Worker 实例,terminate 或未撤销时会释放。Service Worker 中明确禁止此 API,因为可能引发长时间持有的内存泄漏,可使用 fetch 与缓存替代。
撤销后之前赋给 img 的 src 还能继续显示吗?
如果图片加载已完成并解码,撤销不会影响已渲染的位图,画面不会消失。但若撤销发生在加载前,浏览器会中止获取,图片无法显示。因此应在确定不再需要该 URL 时才调用。
参考资料
示例用于理解所注明的运行环境与边界;延伸学习可结合原文中的更多案例。