← 返回题目列表

MutationObserver、IntersectionObserver 和 ResizeObserver 分别解决什么问题?

高频 中等 第 11 / 30 题 更新于 2026/07/29
浏览器 APIObserver性能DOM

简化版

MutationObserver 监听 DOM 结构或属性变化,IntersectionObserver 监听元素是否进入视口或容器,ResizeObserver 监听元素尺寸变化。它们都比轮询或频繁同步读取更适合性能敏感场景,但回调中仍要避免重计算和循环触发。

详细版

三者对比:

  • MutationObserver:DOM 子节点、属性、文本变化。
  • IntersectionObserver:曝光、懒加载、无限滚动、进入视口。
  • ResizeObserver:容器尺寸变化、响应式组件、图表自适应。

示例:

const io = new IntersectionObserver((entries) => {
  entries.forEach(entry => {
    if (entry.isIntersecting) loadImage(entry.target)
  })
})

io.observe(img)

面试回答要强调:Observer API 是浏览器提供的异步观察机制,能减少 scroll/resize 事件里手动计算带来的性能压力。

完整版教学

一、为什么需要 Observer API

传统做法里,很多需求靠轮询、scroll 事件、resize 事件或手动读取 DOM 完成。例如图片懒加载要不断判断元素是否进入视口,图表自适应要监听窗口变化,DOM 插件要检测节点插入。

window.addEventListener('scroll', () => {
  const rect = img.getBoundingClientRect()
  if (rect.top < window.innerHeight) loadImage(img)
})

这种方式容易频繁触发布局读取,scroll 高频时还会让主线程压力变大。Observer API 的价值是把观察逻辑交给浏览器调度,让回调在合适时机批量触发。

面试里要说出“异步、批量、由浏览器调度”,而不是只背 API 名字。

二、MutationObserver 观察 DOM 变化

MutationObserver 用来观察 DOM 节点的子节点、属性和文本变化。它替代了旧的 Mutation Events,适合富文本、插件容器、动态节点监控等场景。

const observer = new MutationObserver((records) => {
  for (const record of records) {
    console.log(record.type)
  }
})

observer.observe(document.body, {
  childList: true,
  subtree: true,
  attributes: true
})

如果页面一次插入 100 个节点,Observer 回调通常会收到一批记录,而不是每个变化立刻同步触发一次业务逻辑。批处理能降低开销。

但不要滥用全页面 subtree: true。大型页面 DOM 频繁变化时,观察整个 body 的成本很高,应尽量缩小观察范围。

三、IntersectionObserver 观察可见性

IntersectionObserver 用来观察目标元素和视口或祖先容器的交叉情况。它非常适合图片懒加载、曝光埋点、无限滚动、动画触发。

const observer = new IntersectionObserver((entries) => {
  for (const entry of entries) {
    if (entry.isIntersecting) {
      entry.target.src = entry.target.dataset.src
      observer.unobserve(entry.target)
    }
  }
}, {
  root: null,
  threshold: 0.1
})

threshold: 0.1 表示交叉比例达到 10% 左右时触发。rootMargin 可以提前触发,例如图片距离视口还有 200px 时开始加载。

视口底部
  + rootMargin 200px
      -> 提前加载图片

相比 scroll 里不断 getBoundingClientRect(),IntersectionObserver 更符合浏览器优化路径。

四、ResizeObserver 观察元素尺寸

ResizeObserver 监听元素尺寸变化,不是窗口尺寸变化。它适合组件级响应式,例如图表容器宽度变化、卡片布局变化、编辑器区域变化。

const ro = new ResizeObserver((entries) => {
  for (const entry of entries) {
    const width = entry.contentRect.width
    chart.resize(width)
  }
})

ro.observe(chartContainer)

过去常用 window.resize,但窗口没变不代表元素没变。侧边栏展开、父容器布局改变、字体加载完成,都可能导致元素尺寸变化。

数字例子:一个 dashboard 有 12 个图表,侧栏从 240px 收起到 64px,窗口宽度没变,但图表容器都变宽了。ResizeObserver 能直接观察容器,而不是猜测所有布局来源。

五、三种 Observer 的选择对比

选择 API 时先问“我在观察什么变化”。DOM 结构变了用 MutationObserver,元素可见性变了用 IntersectionObserver,元素尺寸变了用 ResizeObserver。

API观察对象典型场景
MutationObserverDOM 结构、属性、文本富文本、插件、动态节点
IntersectionObserver元素与视口或容器交叉懒加载、曝光、无限滚动
ResizeObserver元素尺寸图表自适应、组件布局

这三者都不是业务状态管理工具。如果你只是想知道 React/Vue 状态变了,不应该用 MutationObserver 反向观察 DOM,而应在框架状态层处理。

记忆钩子:Mutation 看“变没变”,Intersection 看“看没看见”,Resize 看“大小变没变”。

六、回调里要避免循环和重任务

Observer 回调虽然由浏览器调度,但回调里的代码仍运行在主线程。重计算、频繁 setState、同步布局读取都可能造成卡顿。

const ro = new ResizeObserver(() => {
  // 危险:回调里改尺寸,可能继续触发 resize
  box.style.width = box.offsetWidth + 1 + 'px'
})

ResizeObserver 特别容易出现“观察尺寸 -> 回调改尺寸 -> 再触发观察”的循环。解决方式是判断变化阈值、节流、使用 rAF 批量写入,或避免在回调中直接改变被观察元素尺寸。

Observer callback
  -> 收集变化
  -> requestAnimationFrame 批量更新

七、常见误区与追问

  • 误区:Observer API 回调不占主线程。 回调仍执行在主线程,重逻辑照样会卡。
  • 误区:IntersectionObserver 可以替代所有滚动逻辑。 它适合可见性判断,不适合需要逐像素滚动进度的复杂动画。
  • 误区:ResizeObserver 监听的是窗口大小。 它监听元素尺寸,窗口 resize 只是尺寸变化来源之一。
  • 追问:图片懒加载为什么适合 IntersectionObserver? 浏览器能批量计算交叉状态,避免 scroll 中频繁手动测量。
  • 追问:MutationObserver 观察整个 body 有什么风险? 大页面变化频繁时记录量和回调成本很高。
  • 追问:Observer 不用时要做什么? 调用 unobservedisconnect,避免无意义观察和引用残留。

八、加强记忆

Observer API 按“三看”记:Mutation 看 DOM 变化,Intersection 看可见性,Resize 看尺寸。它们让浏览器帮你异步批量观察,但不会帮你消除回调成本。范围要小,回调要轻,不用要断开。