前端性能优化是提升用户体验、降低跳出率以及改善SEO排名的关键环节。本文从核心指标监控出发,系统梳理加载、运行时、网络、构建及监控等维度的优化策略,并附带可直接落地的代码示例。
一、性能指标与测量
使用 Web Vitals 库可以轻松采集 LCP、FID 和 CLS 三项核心 Web 指标。- import { getCLS, getFID, getLCP } from 'web-vitals';
- getCLS(console.log);
- getFID(console.log);
- 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。
除了代码采集,还可以通过以下工具进行性能审计:- npx lighthouse https://example.com --view
- # Chrome DevTools Performance 面板
- # WebPageTest 多地点测试
- curl "https://www.googleapis.com/pagespeedonline/v5/runPagespeed?url=https://example.com"
复制代码
二、加载性能优化
2.1 资源压缩与优化
图片优化建议使用现代格式(WebP / AVIF),并结合响应式图片与懒加载:- <img src="image.webp" alt="描述"
- srcset="image-400w.webp 400w, image-800w.webp 800w"
- sizes="(max-width: 600px) 400px, 800px"
- loading="lazy">
- <picture>
- <source srcset="image.avif" type="image/avif">
- <source srcset="image.webp" type="image/webp">
- <img src="image.jpg" alt="描述" loading="lazy">
- </picture>
复制代码
代码压缩可借助 Vite 内置的 terser 以及代码分割配置:- export default defineConfig({
- build: {
- minify: 'terser',
- terserOptions: {
- compress: {
- drop_console: true,
- drop_debugger: true,
- },
- },
- rollupOptions: {
- output: {
- manualChunks: {
- vendor: ['vue', 'vue-router', 'pinia'],
- utils: ['lodash-es', 'dayjs'],
- },
- },
- },
- },
- });
复制代码
2.2 资源预加载与预获取
关键资源使用 preload,未来可能用到的页面使用 prefetch,第三方连接使用 preconnect:- <link rel="preload" href="/fonts/main.woff2" as="font" type="font/woff2" crossorigin>
- <link rel="preload" href="/js/hero.js" as="script">
- <link rel="prefetch" href="/js/about.js">
- <link rel="preconnect" href="https://api.example.com">
复制代码
也可以根据用户交互智能预加载:- document.addEventListener('mouseover', (e) => {
- if (e.target.tagName === 'A') {
- const url = e.target.href;
- if (isSameOrigin(url)) {
- const link = document.createElement('link');
- link.rel = 'prefetch';
- link.href = url;
- document.head.appendChild(link);
- }
- }
- });
复制代码
2.3 代码分割与懒加载
路由级代码分割使用动态 import:- const routes = [
- { path: '/', component: () => import('@/views/Home.vue') },
- { path: '/about', component: () => import('@/views/About.vue') },
- { path: '/admin', component: () => import('@/views/Admin.vue'), meta: { requiresAuth: true } }
- ];
复制代码
组件级懒加载可借助 Vue 的 defineAsyncComponent:- const HeavyChart = defineAsyncComponent({
- loader: () => import('@/components/HeavyChart.vue'),
- loadingComponent: LoadingSpinner,
- delay: 200,
- timeout: 3000
- });
复制代码
按需加载第三方库:- const loadLodash = async () => {
- const _ = await import('lodash-es');
- return _.default;
- };
复制代码
三、运行时性能优化
3.1 渲染优化
虚拟列表处理大量数据:- <template>
- <div class="virtual-list" ref="listContainer">
- <div :style="{ height: totalHeight + 'px' }">
- <div v-for="item in visibleItems" :key="item.id"
- :style="{ transform: `translateY(${item.offset}px)`, position: 'absolute', top: 0, left: 0, right: 0 }">
- <ItemComponent :item="item" />
- </div>
- </div>
- </div>
- </template>
- <script setup>
- import { ref, computed, onMounted, onUnmounted } from 'vue';
- const props = defineProps({ items: Array, itemHeight: { type: Number, default: 50 } });
- const listContainer = ref(null);
- const scrollTop = ref(0);
- const visibleCount = 20;
- const totalHeight = computed(() => props.items.length * props.itemHeight);
- const visibleItems = computed(() => {
- const start = Math.floor(scrollTop.value / props.itemHeight);
- const end = Math.min(start + visibleCount, props.items.length);
- return props.items.slice(start, end).map((item, index) => ({
- ...item,
- offset: (start + index) * props.itemHeight
- }));
- });
- const handleScroll = () => { scrollTop.value = listContainer.value.scrollTop; };
- onMounted(() => listContainer.value.addEventListener('scroll', handleScroll));
- onUnmounted(() => listContainer.value.removeEventListener('scroll', handleScroll));
- </script>
复制代码
防抖与节流函数实现:- function debounce(func, wait, immediate = false) {
- let timeout;
- return function(...args) {
- const later = () => {
- timeout = null;
- if (!immediate) func.apply(this, args);
- };
- const callNow = immediate && !timeout;
- clearTimeout(timeout);
- timeout = setTimeout(later, wait);
- if (callNow) func.apply(this, args);
- };
- }
- function throttle(func, limit) {
- let inThrottle;
- return function(...args) {
- if (!inThrottle) {
- func.apply(this, args);
- inThrottle = true;
- setTimeout(() => inThrottle = false, limit);
- }
- };
- }
复制代码
3.2 内存优化
通过 AbortController 取消请求避免内存泄漏,使用 WeakMap 自动释放数据:- class DataFetcher {
- constructor() {
- this.abortController = new AbortController();
- this.cache = new Map();
- }
- async fetchData(url) {
- try {
- const response = await fetch(url, { signal: this.abortController.signal });
- const data = await response.json();
- this.cache.set(url, data);
- return data;
- } catch (error) {
- if (error.name === 'AbortError') { console.log('Request aborted'); }
- else throw error;
- }
- }
- destroy() {
- this.abortController.abort();
- this.cache.clear();
- }
- }
- const componentData = new WeakMap();
- function registerComponent(component, data) {
- componentData.set(component, data);
- }
复制代码
3.3 Web Worker 优化
将繁重计算移至 worker:- // 主线程
- const worker = new Worker('./worker.js');
- worker.postMessage({ type: 'PROCESS_DATA', data: largeDataSet });
- worker.onmessage = (e) => updateUI(e.data);
- // worker.js
- self.onmessage = (e) => {
- const { type, data } = e.data;
- if (type === 'PROCESS_DATA') {
- const result = heavyComputation(data);
- self.postMessage(result);
- }
- };
- function heavyComputation(data) {
- return data.map(item => item * 2).filter(x => x > 10);
- }
复制代码
四、网络优化
4.1 HTTP/2 与 HTTP/3
Nginx 启用 HTTP/2 并配置推送:- server {
- listen 443 ssl http2;
- server_name example.com;
- http2_push /js/app.js;
- http2_push /css/style.css;
- tcp_nodelay on;
- tcp_nopush on;
- }
复制代码
4.2 缓存策略
使用 Service Worker 实现缓存优先、网络优先与 stale-while-revalidate 策略:- const CACHE_NAME = 'v1';
- const CACHE_STRATEGIES = {
- static: ['/', '/index.html', '/css/*', '/js/*'],
- api: '/api/*',
- images: { pattern: '/images/*', maxAge: 7 * 24 * 60 * 60 }
- };
- self.addEventListener('fetch', (event) => {
- const url = new URL(event.request.url);
- if (CACHE_STRATEGIES.static.some(p => url.pathname.includes(p))) {
- event.respondWith(cachedFirst(event.request));
- } else if (url.pathname.startsWith('/api/')) {
- event.respondWith(networkFirst(event.request));
- } else {
- event.respondWith(staleWhileRevalidate(event.request));
- }
- });
- async function cachedFirst(request) {
- const cached = await caches.match(request);
- if (cached) return cached;
- const response = await fetch(request);
- if (response.ok) {
- const cache = await caches.open(CACHE_NAME);
- cache.put(request, response.clone());
- }
- return response;
- }
复制代码
4.3 请求合并
批量请求类实现:- class RequestBatcher {
- constructor(batchSize = 10, batchDelay = 100) {
- this.batchSize = batchSize;
- this.batchDelay = batchDelay;
- this.queue = [];
- this.timer = null;
- }
- add(request) {
- return new Promise((resolve, reject) => {
- this.queue.push({ request, resolve, reject });
- this.flush();
- });
- }
- flush() {
- if (this.timer) clearTimeout(this.timer);
- if (this.queue.length >= this.batchSize) this.executeBatch();
- else this.timer = setTimeout(() => this.executeBatch(), this.batchDelay);
- }
- async executeBatch() {
- const batch = [...this.queue];
- this.queue = [];
- try {
- const responses = await Promise.all(batch.map(item => item.request));
- batch.forEach((item, index) => item.resolve(responses[index]));
- } catch (error) {
- batch.forEach(item => item.reject(error));
- }
- }
- }
复制代码
五、构建优化
5.1 依赖分析
查看打包体积:- npx webpack-bundle-analyzer dist/stats.json
- npx source-map-explorer dist/js/*.js
- vite build --analyze
复制代码
Webpack 分包配置:- const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
- module.exports = {
- optimization: {
- splitChunks: {
- chunks: 'all',
- cacheGroups: {
- vendors: { test: /[\\/]node_modules[\\/]/, name: 'vendors', chunks: 'all' },
- default: { minChunks: 2, priority: -10, reuseExistingChunk: true }
- }
- }
- },
- plugins: [new BundleAnalyzerPlugin({ analyzerMode: 'static', openAnalyzer: false })]
- };
复制代码
5.2 Tree Shaking
使用 ES Module 导入并配置 sideEffects:- import { debounce, throttle } from 'lodash-es'; // ✅ tree-shakeable
- // package.json
- {
- "sideEffects": ["*.css", "*.scss"]
- }
- /*#__PURE__*/ function pureFunction() { return 42; }
复制代码
六、监控与分析
6.1 性能监控
自定义 PerformanceMonitor 采集 LCP、CLS 和 FID,并通过 sendBeacon 上报:- class PerformanceMonitor {
- constructor() {
- this.metrics = {};
- this.init();
- }
- init() {
- if ('PerformanceObserver' in window) {
- this.observeLCP();
- this.observeCLS();
- this.observeFID();
- }
- window.addEventListener('load', () => this.recordLoadPerformance());
- }
- observeLCP() {
- new PerformanceObserver((list) => {
- const entries = list.getEntries();
- this.recordMetric('LCP', entries[entries.length - 1].startTime);
- }).observe({ type: 'largest-contentful-paint', buffered: true });
- }
- observeCLS() {
- let clsValue = 0;
- new PerformanceObserver((list) => {
- for (const entry of list.getEntries()) {
- if (!entry.hadRecentInput) clsValue += entry.value;
- }
- this.recordMetric('CLS', clsValue);
- }).observe({ type: 'layout-shift', buffered: true });
- }
- recordMetric(name, value) {
- this.metrics[name] = value;
- this.sendToAnalytics(name, value);
- }
- recordLoadPerformance() {
- const timing = performance.timing;
- this.recordMetric('LoadTime', timing.loadEventEnd - timing.navigationStart);
- }
- sendToAnalytics(name, value) {
- navigator.sendBeacon('/api/performance', JSON.stringify({ metric: name, value, timestamp: Date.now() }));
- }
- getReport() {
- return { ...this.metrics, timestamp: new Date().toISOString(), userAgent: navigator.userAgent, url: window.location.href };
- }
- }
复制代码
6.2 错误监控
全局监听 JS 错误和未处理的 Promise 拒绝:- window.addEventListener('error', (event) => {
- reportError({
- type: 'javascript',
- message: event.message,
- filename: event.filename,
- lineno: event.lineno,
- colno: event.colno,
- error: event.error
- });
- });
- window.addEventListener('unhandledrejection', (event) => {
- reportError({
- type: 'promise',
- message: event.reason?.message || 'Unhandled promise rejection',
- error: event.reason
- });
- });
- function reportError(error) {
- navigator.sendBeacon('/api/error', JSON.stringify({ ...error, timestamp: Date.now(), url: window.location.href, userAgent: navigator.userAgent }));
- console.error('Performance Error:', error);
- }
复制代码
七、实战案例
某电商站点优化前 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。
总结
性能优化是持续过程,核心策略包括:
- 测量先行,用工具了解现状;
- 从影响最大的地方开始渐进优化;
- 建立监控体系确保效果持续;
- 将性能纳入开发流程。
关键要点:图片优化收益最大,代码分割改善首屏加载,合理缓存提升回访体验,运行时优化增强交互流畅性。定期测量、分析、优化,才能使应用始终保持最佳性能。 |