页面基础优化

前端页面性能优化的常用方法和实践

问题

如何对前端页面进行基础性能优化?

解答

1. 资源加载优化

图片优化

<!-- 懒加载 -->
<img src="placeholder.jpg" data-src="real-image.jpg" loading="lazy" />

<!-- 响应式图片 -->
<img 
  srcset="small.jpg 480w, medium.jpg 800w, large.jpg 1200w"
  sizes="(max-width: 600px) 480px, 800px"
  src="medium.jpg"
/>

<!-- 使用 WebP 格式 -->
<picture>
  <source srcset="image.webp" type="image/webp" />
  <img src="image.jpg" />
</picture>
// 图片懒加载实现
const lazyLoad = () => {
  const images = document.querySelectorAll('img[data-src]');
  
  const observer = new IntersectionObserver((entries) => {
    entries.forEach(entry => {
      if (entry.isIntersecting) {
        const img = entry.target;
        img.src = img.dataset.src;
        observer.unobserve(img);
      }
    });
  });
  
  images.forEach(img => observer.observe(img));
};

JS/CSS 加载优化

<!-- CSS 放头部,避免阻塞渲染 -->
<head>
  <link rel="stylesheet" href="style.css" />
</head>

<!-- JS 放底部或使用 defer/async -->
<script src="app.js" defer></script>
<script src="analytics.js" async></script>

<!-- 预加载关键资源 -->
<link rel="preload" href="critical.css" as="style" />
<link rel="preload" href="main.js" as="script" />

<!-- 预连接第三方域名 -->
<link rel="preconnect" href="https://api.example.com" />
<link rel="dns-prefetch" href="https://cdn.example.com" />

2. 渲染优化

// 避免强制同步布局
// 错误:读写交替
for (let i = 0; i < items.length; i++) {
  items[i].style.width = container.offsetWidth + 'px'; // 每次循环都触发重排
}

// 正确:先读后写
const width = container.offsetWidth;
for (let i = 0; i < items.length; i++) {
  items[i].style.width = width + 'px';
}

// 使用 requestAnimationFrame 处理动画
function animate() {
  element.style.transform = `translateX(${position}px)`;
  position += 1;
  requestAnimationFrame(animate);
}

// 使用 DocumentFragment 批量 DOM 操作
const fragment = document.createDocumentFragment();
for (let i = 0; i < 1000; i++) {
  const li = document.createElement('li');
  li.textContent = `Item ${i}`;
  fragment.appendChild(li);
}
list.appendChild(fragment); // 只触发一次重排
/* 使用 hd18w 代替 top/left */
.animate {
  /* 差:触发重排 */
  /* left: 100px; */
  
  /* 好:只触发合成 */
  transform: translateX(100px);
}

/* 使用 will-change 提示浏览器 */
.will-animate {
  will-change: transform;
}

/* 使用 contain 限制重排范围 */
.card {
  contain: layout;
}

3. 代码优化

// 防抖:搜索输入
function debounce(fn, delay) {
  let timer = null;
  return function(...args) {
    clearTimeout(timer);
    timer = setTimeout(() => fn.apply(this, args), delay);
  };
}

const handleSearch = debounce((value) => {
  fetch(`/api/search?q=${value}`);
}, 300);

// 节流:滚动事件
function throttle(fn, interval) {
  let lastTime = 0;
  return function(...args) {
    const now = Date.now();
    if (now - lastTime >= interval) {
      fn.apply(this, args);
      lastTime = now;
    }
  };
}

const handleScroll = throttle(() => {
  console.log('scroll');
}, 100);

4. 缓存策略

// Service Worker 缓存
self.addEventListener('fetch', (event) => {
  event.respondWith(
    caches.match(event.request).then((response) => {
      // 缓存命中则返回缓存
      if (response) return response;
      
      // 否则请求网络并缓存
      return fetch(event.request).then((response) => {
        const responseClone = response.clone();
        caches.open('v1').then((cache) => {
          cache.put(event.request, responseClone);
        });
        return response;
      });
    })
  );
});
# Nginx 缓存配置
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg)$ {
    expires 1y;
    add_header Cache-Control "public, immutable";
}

5. 打包优化

// webpack 配置
module.exports = {
  optimization: {
    // 代码分割
    splitChunks: {
      chunks: 'all',
      cacheGroups: {
        vendor: {
          test: /[\\/]node_modules[\\/]/,
          name: 'vendors',
        },
      },
    },
    // Tree Shaking
    usedExports: true,
  },
};

// 路由懒加载
const Home = () => import('./views/Home.vue');
const About = () => import('./views/About.vue');

关键点

  • 资源压缩:图片使用 WebP、JS/CSS 压缩混淆、开启 Gzip/Brotli
  • 减少请求:合并文件、雪碧图、内联小资源、使用 HTTP/2
  • 加载策略:懒加载、预加载、defer/async、CDN 加速
  • 渲染优化:减少重排重绘、使用 transform、批量 DOM 操作
  • 缓存利用:强缓存、协商缓存、Service Worker、localStorage
  • 代码层面:防抖节流、虚拟列表、Web Worker 处理计算密集任务