封面由 AI 生成,仅作主题示意。
封面由 AI 生成,仅作主题示意。
做一个商品图选择器,用户上传图片,前端用 URL.createObjectURL(file) 给img显示。换一张图片,再创建一个URL;关闭面板以后,上一张图已经看不见了,但代码是否还记得释放它?问题不在界面有没有清空,而在谁拥有这些对象URL的生命周期。
本文只实现单个预览对象的管理,不上传文件,也不处理出图服务。Picset页面介绍商品图制作,[1] 它或其他工具导出的本地素材都可以作为输入;下面的前端代码独立于这些制作工具,不依赖它们提供接口。
先约定一个实例只管理一个预览
对象URL是对Blob等对象的引用,不是永久图片地址。MDN说明,使用完成后应调用revokeObjectURL释放引用。[2][3] 它不应该被当成持久URL存入商品发布数据,也不能把一次页面会话里的blob地址交给他人作为图片来源。
下面让一个小对象持有当前URL。替换时先创建新的,再释放旧的;创建失败则保持旧URL。清空时释放当前URL,销毁后拒绝继续替换。它只管理自己创建的URL,不能随便释放其他组件仍要使用的资源。
AI 技术概念图,不是浏览器运行截图或内存测量结果。
实现所有权,留出测试入口
保存为 preview-owner.js。默认使用浏览器URL接口,参数只用于依赖注入与分支测试,不要求第三方图片工具有任何接口。
function createPreviewOwner(api = URL) {
let current = null;
let disposed = false;
function clear() {
if (current !== null) {
const old = current;
current = null;
api.revokeObjectURL(old);
}
}
return {
get url() { return current; },
replace(file) {
if (disposed) throw new Error('预览已销毁');
if (!(file instanceof Blob) ||
!/^image\//.test(file.type))
throw new TypeError('需要声明为图片的 Blob');
const next = api.createObjectURL(file);
const old = current;
current = next;
if (old !== null) api.revokeObjectURL(old);
return next;
},
clear,
dispose() {
if (disposed) return;
clear();
disposed = true;
}
};
}
选择图片时,执行 img.src = owner.replace(file);清空操作则先移除img的src,再调用owner.clear。组件卸载时调用dispose,同时移除事件监听和DOM引用。不要在img加载完成的瞬间就撤销当前URL;当前图还可能需要预览、另存或继续被用户操作,资源何时不再使用应由组件生命周期决定。
这里的类型检查只看Blob类型与声明的MIME,不验证文件实际能够解码。如果需要错误反馈,要给img安排error处理,或者另做解码检查;不能把“通过replace”展示成图片文件已经有效。损坏文件的URL同样需要释放,失败状态不让资源自动消失。
用真实URL与可控依赖分别检查
本批交付在本机Edge中创建测试图片,验证当前URL可读取、替换后旧URL不可再读取、清空与销毁后的释放。同时用注入接口检查创建失败时旧资源保留、重复销毁不重复释放、非图片输入与销毁后替换被拒绝。运行记录随文章包提供,不是全浏览器兼容声明。
用测试依赖时,createObjectURL仍应返回与URL类似的可追踪对象标识,并记录释放次数。它能检查代码有没有释放调用,却不能代替真实浏览器的资源行为;反过来,只观察浏览器显示了图片,也不能覆盖异常分支。两种验证回答不同问题。
扩展到多图预览时,不要让所有卡片共用一个实例。每张卡片应有自己的所有者,移除卡片时释放对应资源;列表整体卸载时再统一处理。若一个URL还被放大预览窗口共用,则要重新定义所有权或引用管理,不能直接套用这个“一实例一预览”的示例。
异步流程也需要额外约定。例如文件选择后又等待解码或读取元数据,晚完成的旧任务可能覆盖新选择。本文没有实现异步任务取消,接入时应安排选择版本号或取消机制。Object URL的释放、异步结果的顺序、实际文件校验是三件事,不宜用一个“预览完成”标记全部代替。
这个对象的作用很小:让每一个由它创建的预览URL,有明确的替换、清空和销毁去向。把责任放在创建资源的地方,比依靠每个按钮记住一次释放调用,更便于检查后续新增的交互。