前端8个高级性能优化API

39 阅读2分钟

1.requestIdleCallback

浏览器提供的 API,用于在 浏览器空闲时执行低优先级任务,不会阻塞主线程,也不会影响动画和用户交互。

使用场景

  • 非紧急任务(如日志上报,数据同步,预加载等)
  • 避免影响UI渲染卡顿
const idleCallbackId = requestIdleCallback(callback, options);
// 取消回调
cancelIdleCallback(idleCallbackId);
const bigData = Array.from({ length: 10000 }, (_, i) => `Item${i + 1}`)

function processDataInBatches(idleDeadline) {
  // 每次最多处理100条或者直到空闲时间用完
  if (bigData.length > 0 && (idleDeadline.timeRemaining() > 0 || idleDeadline.didTimeout)) {
    const item = bigData.pop()
    console.log('Processing:', item)
  }
  if (bigData.length > 0) {
    requestIdleCallback(processDataInBatches, {
      timeout: 1000,
    })
  } else {
    console.log('✅ All data processed!')
  }
}

// 启动处理(设置超时1000ms,强制执行回调,防止迟迟不执行)
requestIdleCallback(processDataInBatches, { timeout: 1000 })

2.IntersectionObserver

浏览器提供API, 用于异步观察一个元素与其祖先元素或顶级文档视口viewport之间的交集变化, 简言之, 其可以高效监听某个元素是否进入或离开视口, 以及可见部分的比例.

优势

  • 高性能:浏览器原生实现,比传统的滚动事件监听+getBoundingClientRect()计算更高效
  • 异步执行:回调在空闲时期触发,不阻塞主线程
  • 精确控制:可以设置交叉比例阈值和根元素
    • 当交叉比例(intersection ratio)达到指定阈值时,会触发回调函数。

使用场景

  • 懒加载, 当组件或图片进入视口时加载, 提高性能
  • 无限滚动, 监测滚动到底部, 自动加载内容
  • 广告曝光统计, 检测广告是否被用户看到,以进行数据分析

懒加载:

<img class="lazy" data-src="real-image.jpg" src="placeholder.jpg" alt="示例图片">

document.addEventListener("DOMContentLoaded", () => {
  const lazyImages = document.querySelectorAll('.lazy');
  
  const imageObserver = new IntersectionObserver((entries, observer) => {
    entries.forEach(entry => {
      if (entry.isIntersecting) {
        const img = entry.target;
        img.src = img.dataset.src;
        img.classList.remove('lazy');
        observer.unobserve(img); // 加载后停止观察
      }
    });
  }, {
    rootMargin: '100px' // 提前100px加载
  });

  lazyImages.forEach(image => {
    imageObserver.observe(image);
  });
});

无限滚动:

const sentinel = document.querySelector('#sentinel');
let isLoading = false;

const infiniteScrollObserver = new IntersectionObserver(async (entries) => {
  const entry = entries[0];
  if (entry.isIntersecting && !isLoading) {
    isLoading = true;
    try {
      await loadMoreItems(); // 你的数据加载函数
    } finally {
      isLoading = false;
    }
  }
});

infiniteScrollObserver.observe(sentinel);

3.WeakMap

// 传统方式(内存泄漏风险):  
const domDataMap = new Map();  
domDataMap.set(element, { count0 });  
  
// 优化方案:  
const weakMap = new WeakMap();  
weakMap.set(element, { count0 }); // 当element被移除D0M时,关联数据自动GC

4.ResizeObserver

ResizeObserver 是 原生 JavaScript API,用于监听 DOM 元素尺寸的变化,并在尺寸变化时执行回调函数。它可以检测元素的 width 和 height 变化,而 不需要监听 window.resize 事件

// 传统响应式布局  
window.addEventListener(  
"resize",  
  debounce(() => {  
    const width = container.offsetWidth;  
    adjustElements(width);  
  }, 200),  
);  
  
// 优化方案  
const resizeObserver = new ResizeObserver(entries => {  
for (let entry of entries) {  
    console.log('Element:', entry.target);  
    console.log('Content Rect:', entry.contentRect);  
    console.log('Width:', entry.contentRect.width);  
    console.log('Height:', entry.contentRect.height);  
  }  
});  
  
const element = document.querySelector('#myElement');  
  
resizeObserver.observe(element);

5.Web Worker

浏览器提供的多线程技术,用于 在后台运行 JavaScript 代码,避免主线程阻塞,提高应用的 性能和响应速度

主线程与worker线程通过postMessage/onmessage进行通信

// 主线程
const imageWorker = new Worker('image-worker.js');
// 发送图像数据到Worker
imageWorker.postMessage({
  cmd: 'applyFilter',
  imageData: imageData,
  filterType: 'grayscale'
});

// 接收Worker处理结果
imageWorker.onmessage = (e) => {
  // ......
};

// 错误处理
imageWorker.onerror = (error) => {
  // ......
};

// image-worker.js
// 监听主线程消息
self.onmessage = async (e) => {
    const { cmd, imageData, filterType } = e.data;
    if (cmd === 'applyFilter') {
        let result;
        switch (filterType) {
          case 'grayscale':
            result = await applyGrayscale(imageData);
            break;
          // 可扩展其他滤镜...
        }
        // 返回处理结果
        self.postMessage(result);
    }
};

6.requestAnimationFrame

浏览器提供的 高性能动画 API,用于在 下一帧渲染时执行回调函数,使动画更加 流畅,并且 降低 CPU/GPU 资源消耗

function animate() {
  // 动画逻辑......
  requestAnimationFrame(animate);
}
requestAnimationFrame(animate);

7.URL.createObjectURL

浏览器提供的 API,用于创建 Blob 或 File 对象的临时URL,可以用来 本地预览文件(如图片、视频)、下载文件,而不需要上传到服务器, 需要手动释放内存(URL.revokeObjectURL())

// 导出Blob数据为可下载链接
const blob = new Blob([JSON.stringify(data)], {type: 'application/json'});
const url = URL.createObjectURL(blob); // 直接复制给 img.src / a.href 用于预览或下载
const a = document.createElement('a');
a.href = url;
a.download = 'data.json';
a.click();
URL.revokeObjectURL(url);

8.MutationObserver

异步监测DOM树变化的能力,可以高效地观察特定DOM节点的添加、移除或属性修改

<!-- 基础HTML结构 -->
<div id="observable-container">
  <div class="item">初始项目</div>
</div>
<button id="add-btn">添加项目</button>
<button id="change-btn">修改属性</button>

<script>
  const container = document.getElementById('observable-container');
  const observer = new MutationObserver((mutations) => {
    mutations.forEach(mutation => {
      console.log('检测到变化类型:', mutation.type);
      
      if (mutation.addedNodes.length > 0) {
        console.log('新增节点:', mutation.addedNodes);
      }
      
      if (mutation.attributeName) {
        console.log(`属性 ${mutation.attributeName} 被修改`, 
          mutation.target.getAttribute(mutation.attributeName));
      }
    });
  });

  // 配置观察选项
  const config = {
    childList: true,     // 观察子节点变化
    attributes: true,    // 观察属性变化
    subtree: true,       // 观察所有后代节点
    attributeOldValue: true, // 记录旧属性值
    attributeFilter: ['class', 'data-status'] // 只观察特定属性
  };

  // 开始观察
  observer.observe(container, config);

  // 测试操作
  document.getElementById('add-btn').addEventListener('click', () => {
    const newItem = document.createElement('div');
    newItem.className = 'item';
    newItem.textContent = `新项目 ${new Date().toLocaleTimeString()}`;
    container.appendChild(newItem);
  });

  document.getElementById('change-btn').addEventListener('click', () => {
    container.firstElementChild.classList.toggle('highlight');
    container.setAttribute('data-status', 'modified');
  });

  // 停止观察
  // observer.disconnect();
</script>

9. Performance.now() 精准测量性能

// 获取当前高精度时间(毫秒,精确到微秒)  
const start = performance.now();  
  
// 执行一个耗时操作  
doSomething();  
  
// 再次获取时间  
const end = performance.now();  
  
// 计算耗时,结果非常精确  
console.log(`耗时: ${end - start}ms`);

10. preload & prefetch 资源预加载

特性preloadprefetch
加载时机当前页面 必需资源,高优先级下个页面 可能资源,低优先级
适用场景关键资源(字体、首屏CSS/JS)预测用户下一步需要的资源(如下一页JS)
缓存行为存入 HTTP 缓存,立即执行存入 HTTP 缓存,按需执行

preload: 强制浏览器立即高优先级加载当前页面必需的资源,避免渲染阻塞。

<!-- 关键字体 -->
<link rel="preload" href="font.woff2" as="font" crossorigin>
<!-- 首屏CSS -->
<link rel="preload" href="critical.css" as="style">
<!-- 首屏JS -->
<link rel="preload" href="main.js" as="script">

prefetch: 空闲时低优先级加载后续页面可能需要的资源,提升后续导航体验。

<!-- 预加载下一个页面的JS -->
<link rel="prefetch" href="next-page.js" as="script">

<!-- 预加载图片 -->
<link rel="prefetch" href="hero-image.jpg" as="image">

11. Cache API + Service Worker 离线可用

PWA核心就是缓存, 将静态资源存到客户端, 首次访问正常加载, 二次访问从缓存读取, 离线也能访问.

// service-worker.js  
self.addEventListener('fetch', event => {  
  // 拦截网络请求  
  event.respondWith(  
    // 先在缓存中查找是否有匹配的请求  
    caches.match(event.request).then(cached => {  
      // 如果缓存中有,直接返回缓存内容  
      // 否则发起网络请求  
      return cached || fetch(event.request);  
    })  
  );  
});

12. Web Worker 开启子线程处理重任务

处理大数据量, 导致页面卡死. 通过 Web Worker 将任务放在后台线程执行, 处理完毕后再通知主线程, 保证页面不卡顿.

// main.js - 主线程  
// 创建一个 Web Worker,运行 worker.js 文件  
const worker = new Worker('worker.js');  
  
// 发送数据给 Worker  
worker.postMessage(data);  
  
// 监听 Worker 的返回结果  
worker.onmessage(e) => {  
    console.log('处理完成:', e.data);  
};  
  
// worker.js - 后台线程  
// 监听来自主线程的消息  
self.onmessagefunction(e) {  
// 执行耗时的数据处理  
const result = heavyProcess(e.data);  
    // 将结果返回给主线程  
  self.postMessage(result);  
};

13. document.visibilityState页面不可见时节省资源

切到别的标签页,页面还在疯狂发请求、跑动画?

document.addEventListener('visibilitychange', () => {  
// visibilityState 的值可能是:  
// 'visible':页面在前台  
// 'hidden':页面在后台(最小化、切标签)  
if (document.visibilityState === 'hidden') {  
    // 停止定时轮询接口  
    stopPolling();  
  } else {  
    // 页面回到前台,恢复操作
    reStartPolling();  
  }  
});