无限滚动节点回收的核心是“按需保活”,即仅保留视口及缓冲区(如±3项)的DOM,其余卸载或复用;需结合IntersectionObserver、高度占位、缓存池与轻量回调保障滚动一致性与性能。

用 IntersectionObserver 做无限滚动的节点回收,核心不是“全量渲染”,而是“按需保活”——只让视口附近(含缓冲区)的节点保持 DOM 存在,其余全部卸载或复用。
只保留可视区域 ± N 个区块的 DOM
滚动时不断有新内容插入,若不清理,DOM 节点数会线性增长,触发重排、内存飙升甚至页面卡死。IntersectionObserver 不负责加载,但能精准判断哪些节点“已彻底滚出可回收范围”。
- 设定一个安全缓冲区(如 ±3 个 item 高度),视口内及缓冲区内的节点保持激活;
- 对完全离开缓冲区的节点(
isIntersecting === false且intersectionRatio === 0),触发卸载逻辑:移除事件监听、清空数据引用、调用removeChild()或移交至虚拟池复用; - 注意:不要仅靠
isIntersecting === false判断回收,需结合boundingClientRect和滚动方向做二次过滤,避免快速滚动时误删即将进入视口的节点。
配合虚拟滚动容器做高度占位与动态锚定
真实 DOM 只渲染“当前活跃段”,但滚动条高度要反映全部内容长度,否则滚动体验断裂。需用一个空容器撑开总高度,并动态更新其 height 或使用 padding-top/bottom 占位。
- 维护一个
totalHeight和每个 item 的预估/实测高度映射表; - 当回收顶部节点时,同步减少容器顶部 padding,并偏移内部滚动位置(
scrollTop修正);回收底部节点时同理处理 bottom padding; - 用
getBoundingClientRect()校准首尾 item 实际位置,避免因字体加载、图片异步渲染导致的高度误差累积。
用 observer 复用而非销毁,降低 GC 压力
频繁创建/删除 DOM 容易触发 V8 垃圾回收抖动。更优做法是把离屏节点转入“缓存池”,保留结构和事件绑定,仅清空数据和样式。
- 为每个 item 设唯一 key,回收时存入 Map 池:
cache.set(key, node); - 新 item 渲染前先查池中是否有同类型可用节点,有则
node.replaceChildren(...)+ 重绑定数据,避免重新 createElement; - 限制池大小(如最多缓存 20 个),超限时用 LRU 策略淘汰最久未用节点。
规避 observer 自身性能陷阱
Observer 本身轻量,但不当使用会反成瓶颈:
- 不要为每个 item 单独 new 一个 observer——共用一个实例,靠
target区分节点; - 设置
threshold: [0, 0.1, 0.5, 0.9, 1]而非默认 [0],减少回调频率; - 在回调中避免同步执行复杂操作(如遍历大量节点、触发强制重排),用
requestIdleCallback或微任务延迟处理回收逻辑; - 滚动极快时,可加节流(如 60ms 内最多处理一次 intersection 变化),优先保障主线程流畅。
不复杂但容易忽略:回收不是“删了就完事”,关键在维持滚动一致性、复用粒度控制、以及 observer 回调的轻量化。真正稳定的无限滚动,80% 功夫在回收策略,而不是加载逻辑。



















