toolgarden.xyz
EN
浏览器内存Blob URL

为什么 URL.createObjectURL() 用完需要 revoke?

理解 Blob URL 的资源引用、垃圾回收边界与下载竞态,用明确的创建者和清理函数管理预览生命周期。

ToolGarden 推荐的工具优先在浏览器本地运行,文件和文本不必上传到服务器,适合更注重安全隐私的日常处理。

发布于 2026年10月10日约 8 分钟阅读作者 ToolGarden

URL.createObjectURL(blob) 返回的短字符串不是文件内容,而是浏览器管理的一条资源映射。字符串离开作用域,不等于这条映射立即失效。长期运行的图片、音频或压缩工具如果不断生成新 URL,就需要在旧结果不再使用时撤销它们。

短 URL 为什么能留住大资源?

可以把关系理解为:界面持有 URL 字符串,浏览器的 URL 注册记录关联 Blob,Blob 关联底层数据。底层数据可能在内存或其他浏览器管理的存储里,不能根据 URL 字符串只有几十个字符就推断成本很小。

对同一个 Blob 调用两次 createObjectURL,会得到两个独立的 URL;撤销其中一个不会撤销另一个。它们不一定各复制一份 Blob 字节,但每条未释放的映射都能延长资源可用时间。业务上应为每个结果保存一个明确的 URL,而不是每次渲染都创建新的。

revoke 做了什么,又没有做什么?

操作含义不代表什么
URL.revokeObjectURL(url)撤销这一条 URL 映射不删除用户已经下载的文件
释放 Blob 的 JS 引用让一条 JS 可达路径消失不自动撤销另一个 Object URL
移除 img / video结束当前界面的消费不保证所有原生缓存立即回收
关闭文档浏览器通常清理所属 Object URL不能替代长会话里的主动清理

revoke 后不要再用这个地址开始新读取。它不是强制垃圾回收指令,也不是“立即降低任务管理器数字”的承诺:其他 Blob 引用、已解码像素、仍在消费的资源或浏览器缓存都可能继续存在。排查时要看多轮操作后的趋势,而不是只看 revoke 下一行的数值。

给预览一个明确的所有者

下面示例让同一结果拥有图片和下载链接,返回一个幂等清理函数。示例输入是 PNG Blob;集成时在结果替换、清空或视图卸载时调用它,并在替换前处理旧结果。不要把最后的清理调用紧跟在创建后面,否则图片还没读到数据,URL 就已经无效。

function mountBlobPreview(blob: Blob, host: HTMLElement): () => void {
  const url = URL.createObjectURL(blob);
  const image = document.createElement('img');
  const download = document.createElement('a');
  image.alt = 'Generated image preview';
  image.src = url;
  download.href = url;
  download.download = 'result.png'; // This example expects a PNG Blob.
  download.textContent = 'Download PNG';
  host.append(image, download);

  let disposed = false;
  return () => {
    if (disposed) return;
    disposed = true;
    image.removeAttribute('src');
    download.removeAttribute('href');
    image.remove();
    download.remove();
    URL.revokeObjectURL(url);
  };
}

// Keep this disposer with the current result.
const disposePreview = mountBlobPreview(pngBlob, resultContainer);
// Call disposePreview() when this result is retired or the view unmounts,
// not immediately after mounting or starting a download.

React 中同样应在 effect 内创建 URL,并让该次 effect 的 cleanup 只撤销自己创建的 URL。不要在 render 或 useMemo 中调用 createObjectURL 作为无清理的副作用;开发模式下重复挂载也能暴露生命周期错误。异步生成结果时要检查任务 ID:被替换的旧任务即使晚到,也不能把过期 URL 写回界面。

为什么不能在 img.onload 里总是立即释放?

加载完成表示图片已被读取,不表示用户不再需要原始地址。如果用户还要右键保存、在新标签打开,或多个元素共享这个 URL,提前撤销会让后续操作失败。用于一次性解码、确定没有后续消费者时可以结束生命周期;持续展示的预览则通常保留到移除。视频还可能继续读取或跳转,不能照搬单张图片的时机。

下载之后等几秒再释放就可靠吗?

a.click() 不是下载完成事件,浏览器也没有通用的 anchor 下载完成回调。紧跟 click 撤销可能和资源消费产生竞态;setTimeout 延迟一秒或一分钟都只是经验策略,不是完成证明。对于结果页,保留 URL 让用户重试下载,在结果退役时再清理,并在目标浏览器测试开始下载后切换结果的行为。

若业务需要精确知道写入是否结束,可在支持环境中使用用户授权的文件写入流并等待关闭成功;这与普通下载链接是不同的交互和兼容性条件。不要为了省一个资源引用,把用户仍可能使用的下载入口变成失效链接。

如何检查清理有没有漏?

  1. 在开发环境记录每个结果的创建和撤销次数,不记录 Blob 的敏感内容。
  2. 连续生成、替换、清空十轮,确认已退役结果的 URL 都有撤销记录,活动结果数量有上限。
  3. 检查预览、下载、右键保存、取消和离开页面后的行为,确认没有过早撤销。
  4. 观察进程内存是否趋于稳定;若仍增长,继续检查 state 中的 Blob、ImageBitmap、Canvas 与 Worker,而不是反复调用 revoke。

常见问题

Q.把 url 变量设成 null 就够了吗?

不够。丢掉字符串引用不会撤销浏览器注册的 URL,需要在所有者清理阶段显式 revoke。

Q.revoke 后下载好的文件会消失吗?

不会。它撤销浏览器中的临时 URL,不会删除已保存到磁盘的文件;但尚未消费该 URL 的操作可能失败。