WeakMap 是管理 DOM 节点与私有动画状态映射的理想工具:它不阻止节点被垃圾回收,避免内存泄漏,且保证上下文仅对持有该节点的代码可见;相比 Map 或普通对象,WeakMap 以节点为键不触发字符串转换、无需手动清理、自动随节点销毁而失效。

WeakMap 是管理 DOM 节点与私有动画状态之间映射的理想工具:它不阻止节点被垃圾回收,避免内存泄漏,同时保证上下文仅对持有该节点的代码可见。
为什么用 WeakMap 而不是 Map 或对象?
DOM 节点可能随时被移除(如 innerHTML 重写、remove()、路由切换)。若用普通对象或 Map 存储动画状态:
- 节点被移除后,状态仍保留在 Map 中 → 内存泄漏
- 需手动监听节点销毁(如 MutationObserver)并清理 → 复杂且易遗漏
- 用节点作对象属性名会触发隐式字符串转换(
obj[node]→obj["[object HTMLDivElement]"]),无法唯一标识
WeakMap 允许节点作为键,且不阻止其被 GC —— 节点一消失,对应条目自动失效,无需手动清理。
典型结构:节点 ↔ 动画参数 + 控制句柄
一个常见模式是把当前帧状态、目标值、时间戳、甚至 cancelId 封装为上下文对象,用 WeakMap 关联到节点:
const animationContext = new WeakMap();
<p>function startFadeIn(el, duration = 300) {
const startTime = performance.now();
const from = 0;
const to = 1;</p><p>// 创建私有上下文,只对该节点有效
const ctx = {
startTime,
from,
to,
duration,
lastTime: startTime,
isRunning: true
};</p><p>animationContext.set(el, ctx);</p><p>function frame(time) {
const ctx = animationContext.get(el);
if (!ctx || !ctx.isRunning) return;</p><pre class="brush:php;toolbar:false;">const elapsed = time - ctx.startTime;
const progress = Math.min(elapsed / ctx.duration, 1);
const value = ctx.from + (ctx.to - ctx.from) * progress;
el.style.opacity = value;
if (progress <p>}</p><div class="aritcle_card flexRow artxards">
<div class="artcardd flexRow">
<a class="aritcle_card_img" rel="nofollow" href="/xiazai/skill3430" title="Alibabacloud Sdk Client Initialization For Java"><img
src="https://img.php.cn/upload/skill/000/000/081/178955835420587.jpg" alt="Alibabacloud Sdk Client Initialization For Java" onerror="this.onerror='';this.src='/static/lhimages/moren/morentu.png'" ></a>
<div class="aritcle_card_info flexColumn">
<a rel="nofollow" href="/xiazai/skill3430" title="Alibabacloud Sdk Client Initialization For Java" class="overflowclass">Alibabacloud Sdk Client Initialization For Java</a>
<p class="overflowclass">在 Java 中初始化和管理阿里云 SDK客户端。包括单例模式、线程安全、endpoint 与 region 配置、VPC 终端节点、同步与异步等。</p>
</div>
<a rel="nofollow" href="/xiazai/skill3430" title="Alibabacloud Sdk Client Initialization For Java" class="aritcle_card_btn flexRow flexcenter"><b></b><span>下载</span>
</a>
</div>
</div><p>requestAnimationFrame(frame);
}</p>进阶:支持暂停/恢复与多动画共存
单个节点可能同时运行多个动画(如位移 + 透明度)。WeakMap 的值可以是数组或 Map,按动画类型组织:
const nodeAnimations = new WeakMap();
<p>function animate(el, key, { from, to, duration }) {
let animations = nodeAnimations.get(el);
if (!animations) {
animations = new Map();
nodeAnimations.set(el, animations);
}</p><p>const startTime = performance.now();
const ctx = { from, to, duration, startTime, lastTime: startTime };</p><p>animations.set(key, ctx);</p><p>function tick(time) {
const ctx = animations.get(key);
if (!ctx) return;</p><pre class="brush:php;toolbar:false;">const elapsed = time - ctx.startTime;
const progress = Math.min(elapsed / ctx.duration, 1);
const value = ctx.from + (ctx.to - ctx.from) * progress;
if (key === 'opacity') el.style.opacity = value;
if (key === 'translateX') el.style.transform = `translateX(${value}px)`;
if (progress <p>}</p><p>requestAnimationFrame(tick);
}</p><p>// 使用
animate(myDiv, 'opacity', { from: 0, to: 1, duration: 200 });
animate(myDiv, 'translateX', { from: -50, to: 0, duration: 300 });</p>注意事项与边界处理
WeakMap 本身不可迭代,也不能检查某节点是否有上下文 —— 这是设计使然,也是隐私保障。因此实际使用中需配合显式状态管理:
- 启动动画前,先
animationContext.get(el)判断是否已有活跃动画,决定覆盖或排队 - 调用
el.remove()或替换父级内容前,可主动调用animationContext.delete(el)(非必须,但有助于调试和资源及时释放) - 避免在帧函数中反复调用
get()多次;建议在闭包中缓存一次,或把上下文传入帧回调 - 不要依赖 WeakMap 来“监听”节点销毁 —— 它不提供钩子,GC 时机不可控;业务逻辑中仍需响应 DOM 移除事件(如
el.addEventListener('remove', ...))做副作用清理
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










