WeakRef 不能直接实现 LRU 缓存,需配合 FinalizationRegistry 构建混合方案:用 Map 强引用维护 LRU 顺序并存储解码后资源,WeakRef 仅作 GC 信号,registry 回调中异步清理过期项。

WeakRef 本身不能直接实现 LRU 缓存,因为它不提供访问顺序或使用频次信息,但可以和 FinalizationRegistry 配合,构建「弱引用 + 显式 LRU 管理」的混合方案——核心思路是:用强引用维护 LRU 有序链表(如 Map 或双向链表),同时为每个缓存项注册 WeakRef 和清理回调,让对象被 GC 回收时自动从 LRU 结构中移除,避免内存泄漏。
用 Map 维护 LRU 顺序,WeakRef 不参与排序
LRU 的“最近最少使用”逻辑必须由强引用结构承载。推荐用 Map(插入顺序天然保留):
- 每次
get(key)时,先delete再set,把对应 entry 移到末尾 - 每次
set(key, value)前检查容量,超限时用Map.keys().next().value取最老 key 并删除 -
关键点:Map 中存储的是图片数据(如
HTMLImageElement、Blob或解码后的ImageBitmap)的强引用,不是 WeakRef
为每个缓存项绑定 WeakRef + FinalizationRegistry 清理
在将图片加入 Map 同时,创建 WeakRef 指向它,并注册 FinalizationRegistry 回调:
- WeakRef 不用于读取(因为可能已失效),仅作为 GC 触发信号
- FinalizationRegistry 的回调中传入唯一 key(如 url 或 hash),收到通知后立即从 Map 中
delete(key) - 注册时需确保 key 可序列化且全局唯一,避免误删;建议用字符串,不用对象或 symbol
- 注意:FinalizationRegistry 回调是异步且非确定时机的,所以 Map 删除是“尽力而为”,不影响主流程正确性
高频场景下的实用优化技巧
针对图片缓存高频读写、尺寸多变、GC 延迟等现实问题:
- 对大图(如 >2MB)优先使用
ImageBitmap+transferToImageBitmap(),降低主线程压力 - 设置软上限(如 Map 最多 50 项)+ 硬上限(如总字节 ≤ 100MB),后者需手动跟踪 size(例如用
blob.size或 canvas.toBlob 回调估算) - 在页面 visibilitychange 为 hidden 时,主动
clear()非关键缓存,加速释放 - 避免缓存未解码的
HTMLImageElement,应缓存解码后资源(如 ImageBitmap),否则渲染时仍会触发解码阻塞
一个最小可运行骨架示例
不依赖第三方库,纯 JS 实现核心逻辑:
class WeakLRUCache {
constructor(max = 30) {
this.max = max;
this.map = new Map();
this.registry = new FinalizationRegistry(key => this.map.delete(key));
}
set(key, image) {
// 先清理旧引用(若存在)
if (this.map.has(key)) this.map.delete(key);
// 加入 LRU 末端
this.map.set(key, image);
// 注册弱监听
this.registry.register(image, key, { image });
}
get(key) {
const image = this.map.get(key);
if (image) {
this.map.delete(key);
this.map.set(key, image); // 移至末尾
return image;
}
}
prune() {
while (this.map.size > this.max) {
const firstKey = this.map.keys().next().value;
this.map.delete(firstKey);
}
}
}
注意:该示例未处理 size 限制和 visibility 敏感逻辑,实际项目中建议封装成带统计与生命周期钩子的模块。

















