前端性能优化方向

前端性能优化的主要方向和具体措施

问题

前端性能优化有哪些方向?具体有哪些优化措施?

解答

1. 网络层面

减少请求数量

// 合并小图片为雪碧图
.icon-home { background-position: 0 0; }
.icon-user { background-position: -20px 0; }

// 小图片转 Base64
const img = 'data:image/png;base64,iVBORw0KGgo...';

// 使用 CSS/SVG 代替图片
.arrow {
  border: 5px solid transparent;
  border-left-color: #333;
}

减少请求体积

// 1. 代码压缩 - webpack 配置
module.exports = {
  optimization: {
    minimize: true,
    minimizer: [
      new TerserPlugin(),      // JS 压缩
      new CssMinimizerPlugin() // CSS 压缩
    ]
  }
};

// 2. 开启 Gzip - nginx 配置
// gzip on;
// gzip_types text/plain application/javascript text/css;

// 3. 图片压缩 - 使用 WebP 格式
<picture>
  <source srcset="image.webp" type="image/webp">
  <img src="image.jpg" alt="fallback">
</picture>

利用缓存

// 强缓存 - 服务端设置响应头
Cache-Control: max-age=31536000

// 协商缓存
ETag: "abc123"
Last-Modified: Wed, 21 Oct 2023 07:28:00 GMT

// Service Worker 缓存
self.addEventListener('fetch', (event) => {
  event.respondWith(
    caches.match(event.request).then((response) => {
      return response || fetch(event.request);
    })
  );
});

使用 CDN

<!-- 静态资源走 CDN -->
<script src="https://cdn.example.com/vue@3.3.0/vue.min.js"></script>
<link href="https://cdn.example.com/styles/main.css" rel="stylesheet">

2. 渲染层面

减少重排重绘

// 批量修改 DOM - 使用 DocumentFragment
const fragment = document.createDocumentFragment();
for (let i = 0; i < 1000; i++) {
  const li = document.createElement('li');
  li.textContent = `Item ${i}`;
  fragment.appendChild(li);
}
document.getElementById('list').appendChild(fragment);

// 批量修改样式 - 使用 class
// 不好的做法
el.style.width = '100px';
el.style.height = '100px';
el.style.background = 'red';

// 好的做法
el.className = 'box';

// 读写分离 - 避免强制同步布局
// 不好的做法
for (let i = 0; i < elements.length; i++) {
  elements[i].style.width = box.offsetWidth + 'px'; // 每次循环都触发重排
}

// 好的做法
const width = box.offsetWidth; // 先读
for (let i = 0; i < elements.length; i++) {
  elements[i].style.width = width + 'px'; // 后写
}

CSS 优化

/* 使用 hd18w 代替位置属性 */
/* 不好 - 触发重排 */
.move { left: 100px; }

/* 好 - 只触发合成 */
.move { transform: translateX(100px); }

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

/* 避免复杂选择器 */
/* 不好 */
div.container ul li a span { }

/* 好 */
.nav-link-text { }

3. JavaScript 层面

代码分割与懒加载

// 路由懒加载
const routes = [
  {
    path: '/dashboard',
    component: () => import('./views/Dashboard.vue')
  }
];

// 组件懒加载
const HeavyComponent = React.lazy(() => import('./HeavyComponent'));

function App() {
  return (
    <Suspense fallback={<Loading />}>
      <HeavyComponent />
    </Suspense>
  );
}

// 图片懒加载
<img loading="lazy" src="image.jpg" alt="lazy load">

// Intersection Observer 实现
const observer = new IntersectionObserver((entries) => {
  entries.forEach(entry => {
    if (entry.isIntersecting) {
      const img = entry.target;
      img.src = img.dataset.src;
      observer.unobserve(img);
    }
  });
});

长任务优化

// 使用 requestIdleCallback 处理低优先级任务
function processData(data) {
  let index = 0;
  
  function process(deadline) {
    while (index < data.length && deadline.timeRemaining() > 0) {
      // 处理单个数据项
      handleItem(data[index]);
      index++;
    }
    
    if (index < data.length) {
      requestIdleCallback(process);
    }
  }
  
  requestIdleCallback(process);
}

// 使用 Web Worker 处理计算密集型任务
// main.js
const worker = new Worker('worker.js');
worker.postMessage({ data: largeArray });
worker.onmessage = (e) => {
  console.log('计算结果:', e.data);
};

// worker.js
self.onmessage = (e) => {
  const result = heavyCalculation(e.data);
  self.postMessage(result);
};

防抖节流

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

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

// 使用
input.addEventListener('input', debounce(search, 300));
window.addEventListener('scroll', throttle(handleScroll, 100));

4. 资源加载

预加载与预连接

<!-- DNS 预解析 -->
<link rel="dns-prefetch" href="//cdn.example.com">

<!-- 预连接 - 包含 DNS + TCP + TLS -->
<link rel="preconnect" href="https://api.example.com">

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

<!-- 预获取下一页资源 -->
<link rel="prefetch" href="next-page.js">

脚本加载优化

<!-- defer: 异步加载,DOMContentLoaded 前按顺序执行 -->
<script defer src="app.js"></script>

<!-- async: 异步加载,下载完立即执行 -->
<script async src="analytics.js"></script>

<!-- 关键 CSS 内联 -->
<style>
  /* 首屏关键样式 */
  .header { ... }
  .hero { ... }
</style>

<!-- 非关键 CSS 异步加载 -->
<link rel="preload" href="non-critical.css" as="style" 
      onload="this.rel='stylesheet'">

5. 框架层面

React 优化

// 使用 memo 避免不必要的渲染
const Child = React.memo(function Child({ data }) {
  return <div>{data}</div>;
});

// 使用 useMemo 缓存计算结果
const expensiveValue = useMemo(() => {
  return computeExpensiveValue(a, b);
}, [a, b]);

// 使用 useCallback 缓存函数
const handleClick = useCallback(() => {
  doSomething(id);
}, [id]);

// 虚拟列表 - 只渲染可见区域
import { FixedSizeList } from 'react-window';

function VirtualList({ items }) {
  return (
    <FixedSizeList
      height={400}
      itemCount={items.length}
      itemSize={50}
    >
      {({ index, style }) => (
        <div style={style}>{items[index]}</div>
      )}
    </FixedSizeList>
  );
}

Vue 优化

<template>
  <!-- 使用 v-show 代替频繁切换的 v-if -->
  <div v-show="vkonk">频繁切换的内容</div>
  
  <!-- 列表使用唯一 key -->
  <li v-for="item in list" :key="item.id">{{ item.name }}</li>
  
  <!-- 使用 v-once 渲染静态内容 -->
  <div v-once>{{ staticContent }}</div>
</template>

<script setup>
import { computed, shallowRef } from 'vue';

// 使用 computed 缓存计算
const filteredList = computed(() => {
  return list.value.filter(item => item.active);
});

// 大数据使用 shallowRef
const bigData = shallowRef(largeObject);
</script>

关键点

  • 网络优化:减少请求数量和体积、利用缓存、使用 CDN
  • 渲染优化:减少重排重绘、使用 transform/opacity、批量操作 DOM
  • JS 优化:代码分割、懒加载、防抖节流、Web Worker 处理长任务
  • 资源加载:preload/prefetch/preconnect、defer/async 脚本
  • 框架优化:合理使用 memo/computed、虚拟列表处理大数据