面试备战录

113 阅读4分钟

1、使用IntersectionObserver实现图片懒加载的原理是什么?

答:IntersectionObserver是浏览器提供的原生 API,用于监听一个元素与其父容器或视口(viewport) 的交叉状态(是否进入可视区域)。

懒加载的核心思路:

  • 页面中图片或资源先只设置占位(如灰色背景或小图),真实图片不加载;
  • 当图片即将进入可视区域时,IntersectionObserver回调触发;
  • 回调里把data-src等真实图片地址赋值给src
  • 浏览器开始加载真实图片:
    • 初始化观察器
    const observer = new IntersectionObserver(
      entries => {
        entries.forEach(entry => {
          if (entry.isIntersecting) {
            // 元素进入视口
            const img = entry.target;
            img.src = img.dataset.src; // 替换真实图片
            observer.unobserve(img); // 取消观察,避免重复触发
          }
        });
      },
      {
        root: null, // 默认为 viewport
        rootMargin: '0px',
        threshold: 0.1 // 元素 10% 可见时触发
      }
    );
    
    • 将要懒加载的图片加入观察
    document.querySelectorAll('img[data-src]').forEach(img => {
      observer.observe(img);
    });
    
    • 图片进入可视区域 → 回调触发 → 加载真实图片
      • root:指定观察的容器(可选,默认为 viewport
      • threshold:可见比例阈值,0~1,例如 0.1 表示 10% 可见时触发
      • rootMargin:可视区域的边距,用于提前或延迟触发
    • 优点:
      • 原生API:性能优于滚动事件监听(不用频繁触发scroll回调)
      • 懒加载性能优化:减少首屏资源、降低带宽消耗
      • 灵活:可用于图片、组件、广告、表格行、无限滚动等

2、DOM 操作非常频繁时如何优化性能?

答:浏览器渲染流程:JS 执行 → 修改 DOM → 样式计算(Reflow)→ 重绘(Repaint);频繁DOM操作 会导致 多次 Reflow/Repaint,开销大,性能下降,尤其是大列表或动画场景。目标是 减少DOM操作次数、批量更新、减少渲染开销。

  • 批量操作DOM / 使用DocumentFragment
    • 将元素先加入DocumentFragment,一次性appendDOM
const fragment = document.createDocumentFragment();
for (let i = 0; i < 1000; i++) {
  const li = document.createElement('li');
  li.textContent = `Item ${i}`;
  fragment.appendChild(li);
}
document.querySelector('#list').appendChild(fragment);
  • 使用虚拟DOM / 框架优化
    • React / Vue通过虚拟DOM对比前后状态,只更新必要节点
    • 减少直接操作真实DOM,降低渲染开销
  • 避免触发多次重排
    • 修改样式或布局属性时,避免读写交叉:
    • 批量修改样式时可使用classListCSS 变量
// 不推荐
const height = el.offsetHeight;
el.style.width = '100px';
// 推荐:先修改后读取或缓存值
el.style.width = '100px';
const height = el.offsetHeight;
  • 使用requestAnimationFrame
    • 动画或滚动场景,频繁修改DOM可用requestAnimationFrame批量执行,保证每帧只渲染一次
  • 使用CSS3动画替代JS动画
    • 尽量使用transform / opacity,避免触发布局重排;
    • GPU加速渲染,减少JS操作和重绘;
  • 虚拟列表 / 懒加载
    • 对大量列表或表格,只渲染可视区域的DOM
    • 滚动时动态渲染,减少一次性DOM创建
  • 减少不必要的监听器
    • 尽量 委托事件,而不是每个元素绑定事件

3、Shadow DOM 中的 CSS 隔离是如何实现的?你遇到过哪些坑?

答:Shadow DOM提供了原生的CSS隔离机制,shadow root内定义的样式不会影响外部,外部样式也不会渗透进来。它是通过浏览器在渲染时为选择器加作用域标记实现的。常见的坑有:全局样式无法生效、CSS框架不兼容、调试不方便,以及需要通过::partCSS variables等方式暴露主题接口。Shadow DOMWeb Components的核心之一,它提供了一种样式作用域隔离的能力。

  • 样式只在Shadow DOM内生效
    • Shadow root内定义的CSS规则,只会应用于shadow内部的元素,不会“泄露”到外部。
    • 外部样式(页面全局CSS)默认也不会影响Shadow DOM内部。
  • 选择器作用范围限制
    • Shadow内部的style标签只影响当前shadow root
    • 全局的.btn {}不会影响shadow内的<button>
  • 特性选择器实现隔离(浏览器内部机制)
    • 浏览器在渲染时,会给shadow DOM元素加上特殊的 “scoped attribute”(类似hash标记),使选择器只匹配到自己范围内的节点。
    • 这就是为什么shadow DOM内的CSS能天然隔离,而不像普通scoped CSS需要构建工具处理。

常见的坑点

  • 全局样式无法渗透进Shadow DOM
    • 解决:可以用CSS Custom Properties (变量)来“穿透”:
  • 无法用外部CSS框架(如Tailwind、Bootstrap):因为框架的全局样式不会进入shadow
    • 解决:在组件内手动引入需要的样式。或者用CSS variables + :host机制,暴露出主题接口。
  • 调试困难
    • 开发时用DevToolsDOM,会看到“#shadow-root”分隔,层级比较绕。样式问题经常出现在 host和shadow内部元素之间的交互
  • 样式穿透限制
    • Shadow DOM隔离太“严格”,有时你想自定义某个子组件样式,却发现改不了。
    • 解决:使用::part::theme暴露可控的CSS接口
  • 第三方库不兼容
    • 一些 UI 库(基于全局 CSS)在 Shadow DOM 内直接用会失效。

    • 比如用到document.querySelector或者全局class选择器的库。