查看: 111|回复: 1

前端性能优化实战:从Core Web Vitals监控到代码分割与缓存策略

[复制链接]
发表于 2 小时前 | 显示全部楼层 |阅读模式
前端性能优化是提升用户体验、降低跳出率以及改善SEO排名的关键环节。本文从核心指标监控出发,系统梳理加载、运行时、网络、构建及监控等维度的优化策略,并附带可直接落地的代码示例。

一、性能指标与测量

使用 Web Vitals 库可以轻松采集 LCP、FID 和 CLS 三项核心 Web 指标。
  1. import { getCLS, getFID, getLCP } from 'web-vitals';
  2. getCLS(console.log);
  3. getFID(console.log);
  4. getLCP(console.log);
复制代码
- LCP(最大内容绘制):衡量加载性能,优秀 ≤2.5s,需改进 2.5-4.0s,差 >4.0s。
- FID(首次输入延迟):衡量交互性,优秀 ≤100ms,需改进 100-300ms,差 >300ms。
- CLS(累积布局偏移):衡量视觉稳定性,优秀 ≤0.1,需改进 0.1-0.25,差 >0.25。

除了代码采集,还可以通过以下工具进行性能审计:
  1. npx lighthouse https://example.com --view
  2. # Chrome DevTools Performance 面板
  3. # WebPageTest 多地点测试
  4. curl "https://www.googleapis.com/pagespeedonline/v5/runPagespeed?url=https://example.com"
复制代码

二、加载性能优化

2.1 资源压缩与优化

图片优化建议使用现代格式(WebP / AVIF),并结合响应式图片与懒加载:
  1. <img src="image.webp" alt="描述"
  2.      srcset="image-400w.webp 400w, image-800w.webp 800w"
  3.      sizes="(max-width: 600px) 400px, 800px"
  4.      loading="lazy">
  5. <picture>
  6.   <source srcset="image.avif" type="image/avif">
  7.   <source srcset="image.webp" type="image/webp">
  8.   <img src="image.jpg" alt="描述" loading="lazy">
  9. </picture>
复制代码

代码压缩可借助 Vite 内置的 terser 以及代码分割配置:
  1. export default defineConfig({
  2.   build: {
  3.     minify: 'terser',
  4.     terserOptions: {
  5.       compress: {
  6.         drop_console: true,
  7.         drop_debugger: true,
  8.       },
  9.     },
  10.     rollupOptions: {
  11.       output: {
  12.         manualChunks: {
  13.           vendor: ['vue', 'vue-router', 'pinia'],
  14.           utils: ['lodash-es', 'dayjs'],
  15.         },
  16.       },
  17.     },
  18.   },
  19. });
复制代码

2.2 资源预加载与预获取

关键资源使用 preload,未来可能用到的页面使用 prefetch,第三方连接使用 preconnect:
  1. <link rel="preload" href="/fonts/main.woff2" as="font" type="font/woff2" crossorigin>
  2. <link rel="preload" href="/js/hero.js" as="script">
  3. <link rel="prefetch" href="/js/about.js">
  4. <link rel="preconnect" href="https://api.example.com">
复制代码

也可以根据用户交互智能预加载:
  1. document.addEventListener('mouseover', (e) => {
  2.   if (e.target.tagName === 'A') {
  3.     const url = e.target.href;
  4.     if (isSameOrigin(url)) {
  5.       const link = document.createElement('link');
  6.       link.rel = 'prefetch';
  7.       link.href = url;
  8.       document.head.appendChild(link);
  9.     }
  10.   }
  11. });
复制代码

2.3 代码分割与懒加载

路由级代码分割使用动态 import:
  1. const routes = [
  2.   { path: '/', component: () => import('@/views/Home.vue') },
  3.   { path: '/about', component: () => import('@/views/About.vue') },
  4.   { path: '/admin', component: () => import('@/views/Admin.vue'), meta: { requiresAuth: true } }
  5. ];
复制代码

组件级懒加载可借助 Vue 的 defineAsyncComponent:
  1. const HeavyChart = defineAsyncComponent({
  2.   loader: () => import('@/components/HeavyChart.vue'),
  3.   loadingComponent: LoadingSpinner,
  4.   delay: 200,
  5.   timeout: 3000
  6. });
复制代码

按需加载第三方库:
  1. const loadLodash = async () => {
  2.   const _ = await import('lodash-es');
  3.   return _.default;
  4. };
复制代码

三、运行时性能优化

3.1 渲染优化

虚拟列表处理大量数据:
  1. <template>
  2.   <div class="virtual-list" ref="listContainer">
  3.     <div :style="{ height: totalHeight + 'px' }">
  4.       <div v-for="item in visibleItems" :key="item.id"
  5.            :style="{ transform: `translateY(${item.offset}px)`, position: 'absolute', top: 0, left: 0, right: 0 }">
  6.         <ItemComponent :item="item" />
  7.       </div>
  8.     </div>
  9.   </div>
  10. </template>
  11. <script setup>
  12. import { ref, computed, onMounted, onUnmounted } from 'vue';
  13. const props = defineProps({ items: Array, itemHeight: { type: Number, default: 50 } });
  14. const listContainer = ref(null);
  15. const scrollTop = ref(0);
  16. const visibleCount = 20;
  17. const totalHeight = computed(() => props.items.length * props.itemHeight);
  18. const visibleItems = computed(() => {
  19.   const start = Math.floor(scrollTop.value / props.itemHeight);
  20.   const end = Math.min(start + visibleCount, props.items.length);
  21.   return props.items.slice(start, end).map((item, index) => ({
  22.     ...item,
  23.     offset: (start + index) * props.itemHeight
  24.   }));
  25. });
  26. const handleScroll = () => { scrollTop.value = listContainer.value.scrollTop; };
  27. onMounted(() => listContainer.value.addEventListener('scroll', handleScroll));
  28. onUnmounted(() => listContainer.value.removeEventListener('scroll', handleScroll));
  29. </script>
复制代码

防抖与节流函数实现:
  1. function debounce(func, wait, immediate = false) {
  2.   let timeout;
  3.   return function(...args) {
  4.     const later = () => {
  5.       timeout = null;
  6.       if (!immediate) func.apply(this, args);
  7.     };
  8.     const callNow = immediate && !timeout;
  9.     clearTimeout(timeout);
  10.     timeout = setTimeout(later, wait);
  11.     if (callNow) func.apply(this, args);
  12.   };
  13. }
  14. function throttle(func, limit) {
  15.   let inThrottle;
  16.   return function(...args) {
  17.     if (!inThrottle) {
  18.       func.apply(this, args);
  19.       inThrottle = true;
  20.       setTimeout(() => inThrottle = false, limit);
  21.     }
  22.   };
  23. }
复制代码

3.2 内存优化

通过 AbortController 取消请求避免内存泄漏,使用 WeakMap 自动释放数据:
  1. class DataFetcher {
  2.   constructor() {
  3.     this.abortController = new AbortController();
  4.     this.cache = new Map();
  5.   }
  6.   async fetchData(url) {
  7.     try {
  8.       const response = await fetch(url, { signal: this.abortController.signal });
  9.       const data = await response.json();
  10.       this.cache.set(url, data);
  11.       return data;
  12.     } catch (error) {
  13.       if (error.name === 'AbortError') { console.log('Request aborted'); }
  14.       else throw error;
  15.     }
  16.   }
  17.   destroy() {
  18.     this.abortController.abort();
  19.     this.cache.clear();
  20.   }
  21. }
  22. const componentData = new WeakMap();
  23. function registerComponent(component, data) {
  24.   componentData.set(component, data);
  25. }
复制代码

3.3 Web Worker 优化

将繁重计算移至 worker:
  1. // 主线程
  2. const worker = new Worker('./worker.js');
  3. worker.postMessage({ type: 'PROCESS_DATA', data: largeDataSet });
  4. worker.onmessage = (e) => updateUI(e.data);
  5. // worker.js
  6. self.onmessage = (e) => {
  7.   const { type, data } = e.data;
  8.   if (type === 'PROCESS_DATA') {
  9.     const result = heavyComputation(data);
  10.     self.postMessage(result);
  11.   }
  12. };
  13. function heavyComputation(data) {
  14.   return data.map(item => item * 2).filter(x => x > 10);
  15. }
复制代码

四、网络优化

4.1 HTTP/2 与 HTTP/3

Nginx 启用 HTTP/2 并配置推送:
  1. server {
  2.   listen 443 ssl http2;
  3.   server_name example.com;
  4.   http2_push /js/app.js;
  5.   http2_push /css/style.css;
  6.   tcp_nodelay on;
  7.   tcp_nopush on;
  8. }
复制代码

4.2 缓存策略

使用 Service Worker 实现缓存优先、网络优先与 stale-while-revalidate 策略:
  1. const CACHE_NAME = 'v1';
  2. const CACHE_STRATEGIES = {
  3.   static: ['/', '/index.html', '/css/*', '/js/*'],
  4.   api: '/api/*',
  5.   images: { pattern: '/images/*', maxAge: 7 * 24 * 60 * 60 }
  6. };
  7. self.addEventListener('fetch', (event) => {
  8.   const url = new URL(event.request.url);
  9.   if (CACHE_STRATEGIES.static.some(p => url.pathname.includes(p))) {
  10.     event.respondWith(cachedFirst(event.request));
  11.   } else if (url.pathname.startsWith('/api/')) {
  12.     event.respondWith(networkFirst(event.request));
  13.   } else {
  14.     event.respondWith(staleWhileRevalidate(event.request));
  15.   }
  16. });
  17. async function cachedFirst(request) {
  18.   const cached = await caches.match(request);
  19.   if (cached) return cached;
  20.   const response = await fetch(request);
  21.   if (response.ok) {
  22.     const cache = await caches.open(CACHE_NAME);
  23.     cache.put(request, response.clone());
  24.   }
  25.   return response;
  26. }
复制代码

4.3 请求合并

批量请求类实现:
  1. class RequestBatcher {
  2.   constructor(batchSize = 10, batchDelay = 100) {
  3.     this.batchSize = batchSize;
  4.     this.batchDelay = batchDelay;
  5.     this.queue = [];
  6.     this.timer = null;
  7.   }
  8.   add(request) {
  9.     return new Promise((resolve, reject) => {
  10.       this.queue.push({ request, resolve, reject });
  11.       this.flush();
  12.     });
  13.   }
  14.   flush() {
  15.     if (this.timer) clearTimeout(this.timer);
  16.     if (this.queue.length >= this.batchSize) this.executeBatch();
  17.     else this.timer = setTimeout(() => this.executeBatch(), this.batchDelay);
  18.   }
  19.   async executeBatch() {
  20.     const batch = [...this.queue];
  21.     this.queue = [];
  22.     try {
  23.       const responses = await Promise.all(batch.map(item => item.request));
  24.       batch.forEach((item, index) => item.resolve(responses[index]));
  25.     } catch (error) {
  26.       batch.forEach(item => item.reject(error));
  27.     }
  28.   }
  29. }
复制代码

五、构建优化

5.1 依赖分析

查看打包体积:
  1. npx webpack-bundle-analyzer dist/stats.json
  2. npx source-map-explorer dist/js/*.js
  3. vite build --analyze
复制代码

Webpack 分包配置:
  1. const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
  2. module.exports = {
  3.   optimization: {
  4.     splitChunks: {
  5.       chunks: 'all',
  6.       cacheGroups: {
  7.         vendors: { test: /[\\/]node_modules[\\/]/, name: 'vendors', chunks: 'all' },
  8.         default: { minChunks: 2, priority: -10, reuseExistingChunk: true }
  9.       }
  10.     }
  11.   },
  12.   plugins: [new BundleAnalyzerPlugin({ analyzerMode: 'static', openAnalyzer: false })]
  13. };
复制代码

5.2 Tree Shaking

使用 ES Module 导入并配置 sideEffects:
  1. import { debounce, throttle } from 'lodash-es'; // ✅ tree-shakeable
  2. // package.json
  3. {
  4.   "sideEffects": ["*.css", "*.scss"]
  5. }
  6. /*#__PURE__*/ function pureFunction() { return 42; }
复制代码

六、监控与分析

6.1 性能监控

自定义 PerformanceMonitor 采集 LCP、CLS 和 FID,并通过 sendBeacon 上报:
  1. class PerformanceMonitor {
  2.   constructor() {
  3.     this.metrics = {};
  4.     this.init();
  5.   }
  6.   init() {
  7.     if ('PerformanceObserver' in window) {
  8.       this.observeLCP();
  9.       this.observeCLS();
  10.       this.observeFID();
  11.     }
  12.     window.addEventListener('load', () => this.recordLoadPerformance());
  13.   }
  14.   observeLCP() {
  15.     new PerformanceObserver((list) => {
  16.       const entries = list.getEntries();
  17.       this.recordMetric('LCP', entries[entries.length - 1].startTime);
  18.     }).observe({ type: 'largest-contentful-paint', buffered: true });
  19.   }
  20.   observeCLS() {
  21.     let clsValue = 0;
  22.     new PerformanceObserver((list) => {
  23.       for (const entry of list.getEntries()) {
  24.         if (!entry.hadRecentInput) clsValue += entry.value;
  25.       }
  26.       this.recordMetric('CLS', clsValue);
  27.     }).observe({ type: 'layout-shift', buffered: true });
  28.   }
  29.   recordMetric(name, value) {
  30.     this.metrics[name] = value;
  31.     this.sendToAnalytics(name, value);
  32.   }
  33.   recordLoadPerformance() {
  34.     const timing = performance.timing;
  35.     this.recordMetric('LoadTime', timing.loadEventEnd - timing.navigationStart);
  36.   }
  37.   sendToAnalytics(name, value) {
  38.     navigator.sendBeacon('/api/performance', JSON.stringify({ metric: name, value, timestamp: Date.now() }));
  39.   }
  40.   getReport() {
  41.     return { ...this.metrics, timestamp: new Date().toISOString(), userAgent: navigator.userAgent, url: window.location.href };
  42.   }
  43. }
复制代码

6.2 错误监控

全局监听 JS 错误和未处理的 Promise 拒绝:
  1. window.addEventListener('error', (event) => {
  2.   reportError({
  3.     type: 'javascript',
  4.     message: event.message,
  5.     filename: event.filename,
  6.     lineno: event.lineno,
  7.     colno: event.colno,
  8.     error: event.error
  9.   });
  10. });
  11. window.addEventListener('unhandledrejection', (event) => {
  12.   reportError({
  13.     type: 'promise',
  14.     message: event.reason?.message || 'Unhandled promise rejection',
  15.     error: event.reason
  16.   });
  17. });
  18. function reportError(error) {
  19.   navigator.sendBeacon('/api/error', JSON.stringify({ ...error, timestamp: Date.now(), url: window.location.href, userAgent: navigator.userAgent }));
  20.   console.error('Performance Error:', error);
  21. }
复制代码

七、实战案例

某电商站点优化前 LCP 4.2s、FID 350ms、CLS 0.35、首屏 5.1s。采取图片 WebP+懒加载、路由+组件级代码分割、预加载关键资源、Service Worker 缓存、启用 HTTP/2 等措施后,LCP 降至 1.8s,FID 85ms,CLS 0.08,首屏仅 2.1s。

总结
性能优化是持续过程,核心策略包括:
- 测量先行,用工具了解现状;
- 从影响最大的地方开始渐进优化;
- 建立监控体系确保效果持续;
- 将性能纳入开发流程。

关键要点:图片优化收益最大,代码分割改善首屏加载,合理缓存提升回访体验,运行时优化增强交互流畅性。定期测量、分析、优化,才能使应用始终保持最佳性能。
回复

使用道具 举报

发表于 2 小时前 | 显示全部楼层

Re: 前端性能优化实战:从Core Web Vitals监控到代码分割与缓存策略

楼主这个帖子太实用了!从Core Web Vitals的具体阈值到每步的代码示例,都讲得很清晰,特别是LCP、FID、CLS的衡量标准和优化建议,直接就能对线上项目做对比。Vite配置里的manualChunks拆分vendor和工具库,还有动态import配合defineAsyncComponent的懒加载方案,都是日常开发中很接地气的做法,收藏了慢慢实践。感谢分享!
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 注册

本版积分规则

指导单位

江苏省公安厅

江苏省通信管理局

浙江省台州刑侦支队

DEFCON GROUP 86025

Hacking Group 021A

旗下站点

态势感知中心

应急响应中心

红盟安全

联系我们

官方QQ群:112851260

官方邮箱:security#ihonker.org(#改成@)

官方核心成员

关注微信公众号

Archiver|手机版|小黑屋| ( 沪ICP备2021026908号 )

GMT+8, 2026-6-12 13:59 , Processed in 0.027176 second(s), 18 queries , Gzip On, Redis On.

Powered by ihonker.com

Copyright © 2015-现在.

  • 返回顶部