欢迎光临
我们一直在努力

【前端进阶】前端性能优化完全指南:从加载到渲染

【前端进阶】前端性能优化完全指南:从加载到渲染

引言

前端性能优化是提升用户体验的关键环节。据统计,页面加载时间每增加1秒,用户流失率就会上升7%;超过50%的用户会放弃加载时间超过3秒的网站。本文将从前端性能优化的全链路角度出发,深入讲解从资源加载到页面渲染的各个环节的优化策略,包括网络请求优化、JavaScript执行优化、渲染性能优化、缓存策略以及性能监控与度量,帮助读者构建高性能的Web应用。

一、性能优化的核心指标

1.1 Core Web Vitals

Google提出的Core Web Vitals是评估网页性能的核心指标:

  • LCP (Largest Contentful Paint):最大内容绘制,衡量加载性能,推荐值<2.5秒
  • FID (First Input Delay):首次输入延迟,衡量交互性,推荐值<100毫秒
  • CLS (Cumulative Layout Shift):累积布局偏移,衡量视觉稳定性,推荐值<0.1

// 使用Web Vitals库收集指标
import { onLCP, onFID, onCLS, onFCP, onTTFB } from 'web-vitals';

function sendToAnalytics({ name, value, id }) {
// 发送到分析服务
fetch('/api/analytics', {
method: 'POST',
body: JSON.stringify({ name, value, id })
});
}

onLCP(sendToAnalytics);
onFID(sendToAnalytics);
onCLS(sendToAnalytics);
onFCP(sendToAnalytics);
onTTFB(sendToAnalytics);

// 详细报告
onCLS((metric) => {
const { entries, value } = metric;
entries.forEach(entry => {
console.log('Layout shift source:', entry.sources);
});
});

1.2 性能度量API

// Performance API详解
const perf = performance.getEntriesByType('navigation')[0];

console.log({
// 关键时间节点
dns: perf.domainLookupEnd – perf.domainLookupStart, // DNS查询时间
tcp: perf.connectEnd – perf.connectStart, // TCP连接时间
ssl: perf.secureConnectionStart > 0
? perf.connectEnd – perf.secureConnectionStart : 0, // SSL握手时间
ttfb: perf.responseStart – perf.requestStart, // 首字节时间
download: perf.responseEnd – perf.responseStart, // 内容下载时间
domInteractive: perf.domInteractive, // DOM可交互时间
domComplete: perf.domComplete, // DOM完成时间
loadEvent: perf.loadEventEnd, // Load事件结束时间

// 关键指标
firstPaint: performance.getEntriesByType('paint')[0]?.startTime,
firstContentfulPaint: performance.getEntriesByType('paint')[1]?.startTime,
});

// Resource Timing API
const resources = performance.getEntriesByType('resource');

resources.forEach(resource => {
console.log({
name: resource.name,
duration: resource.duration,
dns: resource.domainLookupEnd – resource.domainLookupStart,
tcp: resource.connectEnd – resource.connectStart,
ttfb: resource.responseStart – resource.requestStart,
transfer: resource.transferSize,
encodedBody: resource.encodedBodySize,
decodedBody: resource.decodedBodySize
});
});

// Long Tasks API – 监控长任务
const observer = new PerformanceObserver((list) => {
list.getEntries().forEach(entry => {
console.log('Long task detected:', {
duration: entry.duration,
startTime: entry.startTime,
attribution: entry.attribution
});
});
});

observer.observe({ type: 'longtask', buffered: true });

二、网络请求优化

2.1 资源压缩与合并

<!– HTML压缩示例 –>
<!– 压缩前 –>
<script>
function calculateSum(arr) {
const sum = arr.reduce((acc, val) => {
return acc + val;
}, 0);
return sum;
}
</script>

<!– 压缩后 (使用Terser) –>
<script>
function s(n){return n.reduce((a,c)=>a+c,0)}s([1,2,3,4,5]);
</script>

// Webpack配置 – 资源压缩与优化
// webpack.config.js
const HtmlWebpackPlugin = require('html-webpack-plugin');
const TerserPlugin = require('terser-webpack-plugin');
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');
const CompressionPlugin = require('compression-webpack-plugin');

module.exports = {
mode: 'production',
optimization: {
minimize: true,
minimizer: [
new TerserPlugin({
terserOptions: {
compress: {
drop_console: true, // 移除console.log
drop_debugger: true,
pure_funcs: ['console.log']
},
output: {
comments: false
}
},
extractComments: false
}),
new CssMinimizerPlugin()
],
splitChunks: {
chunks: 'all',
cacheGroups: {
vendor: {
test: /[\\\\/]node_modules[\\\\/]/,
name: 'vendors',
priority: 10,
reuseExistingChunk: true
},
common: {
minChunks: 2,
priority: 5,
reuseExistingChunk: true
}
}
},
runtimeChunk: 'single'
},
plugins: [
new CompressionPlugin({
filename: '[path][base].gz',
algorithm: 'gzip',
test: /\\.(js|css|html|svg)$/,
threshold: 10240, // 10KB以上才压缩
minRatio: 0.8
})
]
};

2.2 图片优化

<!– 现代图片格式与响应式图片 –>
<!– 响应式图片 – 根据设备加载合适尺寸 –>
<img
src="image-800.jpg"
srcset="
image-320.jpg 320w,
image-640.jpg 640w,
image-1280.jpg 1280w,
image-2560.jpg 2560w
"
sizes="
(max-width: 320px) 280px,
(max-width: 640px) 580px,
(max-width: 1280px) 1200px,
2560px
"
loading="lazy"
decoding="async"
alt="Responsive image"
>

<!– 画布内容适配 (Art Direction) –>
<picture>
<source media="(min-width: 1024px)" srcset="hero-desktop.jpg">
<source media="(min-width: 768px)" srcset="hero-tablet.jpg">
<source media="(min-width: 320px)" srcset="hero-mobile.jpg">
<img src="hero-mobile.jpg" alt="Hero image">
</picture>

<!– WebP格式 –>
<picture>
<source type="image/webp" srcset="image.webp">
<source type="image/jpeg" srcset="image.jpg">
<img src="image.jpg" alt="Image">
</picture>

<!– 懒加载图片 –>
<img data-src="real-image.jpg" class="lazy" alt="Lazy loaded">

// JavaScript图片懒加载实现
class LazyLoader {
constructor(options = {}) {
this.options = {
root: null,
rootMargin: '50px',
threshold: 0.1,
…options
};
this.observer = null;
this.init();
}

init() {
if ('IntersectionObserver' in window) {
this.observer = new IntersectionObserver(
(entries) => this.handleIntersection(entries),
this.options
);
this.observeImages();
} else {
// 降级方案
this.loadAllImages();
}
}

observeImages() {
const images = document.querySelectorAll('img[data-src]');
images.forEach(img => this.observer.observe(img));
}

handleIntersection(entries) {
entries.forEach(entry => {
if (entry.isIntersecting) {
const img = entry.target;
this.loadImage(img);
this.observer.unobserve(img);
}
});
}

loadImage(img) {
const src = img.dataset.src;
const srcset = img.dataset.srcset;

img.src = src;
if (srcset) img.srcset = srcset;

img.addEventListener('load', () => {
img.classList.add('loaded');
img.removeAttribute('data-src');
});
}

loadAllImages() {
document.querySelectorAll('img[data-src]').forEach(img => {
this.loadImage(img);
});
}
}

// 使用
const lazyLoader = new LazyLoader({
rootMargin: '100px',
threshold: 0.1
});

2.3 字体优化

<!– 字体加载优化 –>
<!– font-display: swap 避免FOIT –>
<style>
@font-face {
font-family: 'CustomFont';
font-display: swap; /* 关键:使用备用字体直到自定义字体加载完成 */
src: url('/fonts/custom-font.woff2') format('woff2'),
url('/fonts/custom-font.woff') format('woff');
font-weight: 400;
font-style: normal;
unicode-range: U+0000-00FF, U+0131, U+0152-0153;
}

/* 预加载关键字体 */
<link rel="preload" href="/fonts/custom-font.woff2" as="font" type="font/woff2" crossorigin>
</style>

/* CSS字体加载策略 */
.font-loaded {
font-family: 'CustomFont', system-ui, sans-serif;
}

/* 分层字体加载 */
.text-layer-1 { font-display: optional; } /* 可选,不阻塞渲染 */
.text-layer-2 { font-display: swap; } /* 交换,可能FOUT */
.text-layer-3 { font-display: block; } /* 阻塞,可能FOIT */

/* 使用字体子集 */
@font-face {
font-family: 'ChineseFont';
src: url('/fonts/chinese-subset.woff2') format('woff2');
unicode-range: U+4E00-9FFF; /* 只加载常用汉字 */
}

2.4 DNS预解析与预连接

<!– DNS预解析 –>
<link rel="dns-prefetch" href="https://fonts.googleapis.com">
<link rel="dns-prefetch" href="https://fonts.gstatic.com">
<link rel="dns-prefetch" href="https://cdn.example.com">

<!– 预连接 – 建立早起连接 –>
<link rel="preconnect" href="https://fonts.googleapis.com" crossorigin>
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link rel="preconnect" href="https://cdn.example.com">

<!– 预加载关键资源 –>
<link rel="preload" href="/js/app.js" as="script">
<link rel="preload" href="/css/main.css" as="style">
<link rel="preload" href="/images/hero.webp" as="image" type="image/webp">

<!– 预加载并执行脚本 –>
<link rel="modulepreload" href="/js/app.js">

三、JavaScript执行优化

3.1 代码分割与懒加载

// Webpack代码分割配置
const path = require('path');

module.exports = {
entry: {
main: './src/index.js',
vendor: './src/vendor.js'
},
output: {
filename: '[name].[contenthash].js',
chunkFilename: '[name].[contenthash].chunk.js',
path: path.resolve(__dirname, 'dist'),
clean: true
},
optimization: {
splitChunks: {
chunks: 'all',
maxInitialRequests: 25,
minSize: 20000,
cacheGroups: {
defaultVendors: {
test: /[\\\\/]node_modules[\\\\/]/,
priority: -10,
reuseExistingChunk: true,
name: 'vendors'
},
default: {
minChunks: 2,
priority: -20,
reuseExistingChunk: true
},
// 提取公共库
react: {
test: /[\\\\/]node_modules[\\\\/](react|react-dom)[\\\\/]/,
name: 'react-vendor',
chunks: 'all'
},
// 提取图表库
charts: {
test: /[\\\\/]node_modules[\\\\/](echarts|chart\\.js|d3)[\\\\/]/,
name: 'charts-vendor',
chunks: 'all'
}
}
}
}
};

// React路由级代码分割
import { Suspense, lazy } from 'react';
import { BrowserRouter, Routes, Route } from 'react-router-dom';

// 路由懒加载
const Home = lazy(() => import('./pages/Home'));
const About = lazy(() => import('./pages/About'));
const Product = lazy(() => import('./pages/Product'));
const ProductDetail = lazy(() => import('./pages/ProductDetail'));
const Cart = lazy(() => import('./pages/Cart'));
const User = lazy(() => import('./pages/User'));
const NotFound = lazy(() => import('./pages/NotFound'));

// 加载骨架屏
function LoadingSpinner() {
return (
<div className="loading-spinner">
<div className="spinner"></div>
</div>
);
}

// 应用组件
function App() {
return (
<BrowserRouter>
<Suspense fallback={<LoadingSpinner />}>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="/products" element={<Product />} />
<Route path="/products/:id" element={<ProductDetail />} />
<Route path="/cart" element={<Cart />} />
<Route path="/user/*" element={<User />} />
<Route path="*" element={<NotFound />} />
</Routes>
</Suspense>
</BrowserRouter>
);
}

// 组件级懒加载
const HeavyChart = lazy(() =>
import(/* webpackChunkName: "chart" */ './components/HeavyChart')
);

// 预加载下一步可能需要的组件
function ProductList() {
const handleHover = (productId) => {
// 用户悬停时预加载详情页
import('./pages/ProductDetail');
};

return (
<div>
{products.map(product => (
<div
key={product.id}
onMouseEnter={() => handleHover(product.id)}
>
{/* … */}
</div>
))}
</div>
);
}

3.2 长任务拆分

// 使用requestIdleCallback拆分长任务
function processInIdleCallback(tasks, callback) {
const startTime = performance.now();

function processChunk(deadline) {
while (deadline.timeRemaining() > 0 && tasks.length > 0) {
const task = tasks.shift();
task();
}

if (tasks.length > 0) {
requestIdleCallback(processChunk, { timeout: 1000 });
} else {
callback();
}
}

requestIdleCallback(processChunk, { timeout: 1000 });
}

// 处理大量数据
function processLargeData() {
const data = Array.from({ length: 100000 }, (_, i) => i);
const chunks = [];
const chunkSize = 1000;

for (let i = 0; i < data.length; i += chunkSize) {
chunks.push(() => {
const chunk = data.slice(i, i + chunkSize);
// 处理这个数据块
processChunk(chunk);
});
}

processInIdleCallback(chunks, () => {
console.log('All data processed');
});
}

// Web Worker处理计算密集任务
// worker.js
self.onmessage = function(e) {
const { type, data } = e.data;

switch (type) {
case 'SORT':
const sorted = quickSort(data);
self.postMessage({ type: 'SORT_RESULT', result: sorted });
break;
case 'FILTER':
const filtered = data.filter(item => item.value > 100);
self.postMessage({ type: 'FILTER_RESULT', result: filtered });
break;
default:
self.postMessage({ error: 'Unknown task type' });
}
};

function quickSort(arr) {
if (arr.length <= 1) return arr;
const pivot = arr[Math.floor(arr.length / 2)];
const left = arr.filter(x => x < pivot);
const middle = arr.filter(x => x === pivot);
const right = arr.filter(x => x > pivot);
return […quickSort(left), …middle, …quickSort(right)];
}

// 主线程
const worker = new Worker('/worker.js');

worker.postMessage({ type: 'SORT', data: largeArray });

worker.onmessage = function(e) {
if (e.data.type === 'SORT_RESULT') {
console.log('Sorted:', e.data.result);
}
};

3.3 事件防抖与节流

// 防抖与节流实现
function debounce(func, wait, immediate = false) {
let timeout;

return function executedFunction(…args) {
const context = this;

const later = () => {
timeout = null;
if (!immediate) func.apply(context, args);
};

const callNow = immediate && !timeout;

clearTimeout(timeout);
timeout = setTimeout(later, wait);

if (callNow) func.apply(context, args);
};
}

function throttle(func, limit) {
let inThrottle;
let lastFunc;
let lastRan;

return function executedFunction(…args) {
const context = this;

if (!inThrottle) {
func.apply(context, args);
lastRan = Date.now();
inThrottle = true;
} else {
clearTimeout(lastFunc);
lastFunc = setTimeout(() => {
if (Date.now() – lastRan >= limit) {
func.apply(context, args);
lastRan = Date.now();
}
}, limit – (Date.now() – lastRan));
}
};
}

// React Hook中使用
function useDebounce(value, delay) {
const [debouncedValue, setDebouncedValue] = useState(value);

useEffect(() => {
const handler = setTimeout(() => {
setDebouncedValue(value);
}, delay);

return () => clearTimeout(handler);
}, [value, delay]);

return debouncedValue;
}

function SearchInput() {
const [query, setQuery] = useState('');
const [results, setResults] = useState([]);

// 防抖搜索输入
const debouncedQuery = useDebounce(query, 300);

useEffect(() => {
if (debouncedQuery) {
searchAPI(debouncedQuery).then(setResults);
}
}, [debouncedQuery]);

return (
<div>
<input
value={query}
onChange={e => setQuery(e.target.value)}
/>
{/* 结果列表 */}
</div>
);
}

function InfiniteScrollList() {
const [items, setItems] = useState([]);
const [page, setPage] = useState(1);

// 节流滚动处理
const handleScroll = throttle(() => {
if (
window.innerHeight + window.scrollY >=
document.body.offsetHeight – 500
) {
setPage(p => p + 1);
}
}, 200);

useEffect(() => {
window.addEventListener('scroll', handleScroll);
return () => window.removeEventListener('scroll', handleScroll);
}, [handleScroll]);

return /* … */;
}

四、渲染性能优化

4.1 CSS渲染优化

/* 避免重排与重绘 */
/* 坏的写法 */
.element {
width: 100px;
height: 100px;
background: red;
position: absolute;
top: 0;
left: 0;
}

/* 批量DOM操作后统一触发重排 */
.container {
/* 使用transform替代top/left/right/bottom */
transform: translateZ(0);
/* 或使用will-change提示浏览器 */
will-change: transform;
}

/* 分离读写操作 */
.element {
/* 读取放在一边 */
}
.other-element {
/* 写入放在另一边 */
}

/* 使用CSS containment */
.contain-layout {
contain: layout;
}
.contain-paint {
contain: paint;
}
.contain-strict {
contain: strict;
}

/* 硬件加速 */
.animated-element {
transform: translateZ(0);
backface-visibility: hidden;
perspective: 1000px;
}

/* 减少重排 */
.reading-styles {
/* 统一读取样式 */
}
.updating-styles {
/* 统一修改样式 */
}

// 高效DOM操作
class VirtualList {
constructor(container, items, itemHeight) {
this.container = container;
this.items = items;
this.itemHeight = itemHeight;
this.visibleCount = Math.ceil(container.clientHeight / itemHeight);
this.scrollTop = 0;

this.init();
}

init() {
this.content = document.createElement('div');
this.content.className = 'virtual-list-content';
this.content.style.height = `${this.items.length * this.itemHeight}px`;
this.content.style.position = 'relative';

this.viewport = document.createElement('div');
this.viewport.className = 'virtual-list-viewport';
this.viewport.style.overflow = 'auto';

this.viewport.appendChild(this.content);
this.container.appendChild(this.viewport);

this.viewport.addEventListener('scroll', this.handleScroll.bind(this));
this.render();
}

handleScroll() {
this.scrollTop = this.viewport.scrollTop;
this.render();
}

render() {
const startIndex = Math.floor(this.scrollTop / this.itemHeight);
const endIndex = Math.min(
startIndex + this.visibleCount + 1,
this.items.length
);

// 清空现有渲染
this.content.innerHTML = '';

// 只渲染可见项
for (let i = startIndex; i < endIndex; i++) {
const item = document.createElement('div');
item.className = 'virtual-list-item';
item.style.position = 'absolute';
item.style.top = `${i * this.itemHeight}px`;
item.style.height = `${this.itemHeight}px`;
item.textContent = this.items[i];
this.content.appendChild(item);
}
}
}

4.2 React渲染优化

// React.memo优化组件渲染
const ProductCard = React.memo(function ProductCard({ product, onAddToCart }) {
console.log('ProductCard rendered:', product.id);

return (
<div className="product-card">
<img src={product.image} alt={product.name} />
<h3>{product.name}</h3>
<p>${product.price}</p>
<button onClick={() => onAddToCart(product.id)}>
Add to Cart
</button>
</div>
);
}, (prevProps, nextProps) => {
// 自定义比较逻辑
return (
prevProps.product.id === nextProps.product.id &&
prevProps.product.price === nextProps.product.price &&
prevProps.product.name === nextProps.product.name
);
});

// useMemo和useCallback
function ProductList({ categoryId, filter }) {
// 记忆化过滤后的产品列表
const filteredProducts = useMemo(() => {
console.log('Filtering products…');
return products
.filter(p => p.categoryId === categoryId)
.filter(p => {
if (filter === 'price-asc') return p.price < 100;
if (filter === 'price-desc') return p.price > 100;
return true;
});
}, [products, categoryId, filter]);

// 记忆化排序后的产品列表
const sortedProducts = useMemo(() => {
return […filteredProducts].sort((a, b) => {
if (filter === 'price-asc') return a.price – b.price;
if (filter === 'price-desc') return b.price – a.price;
return 0;
});
}, [filteredProducts, filter]);

// 记忆化回调函数
const handleAddToCart = useCallback((productId) => {
dispatch(addToCart(productId));
}, [dispatch]);

const handleProductClick = useCallback((productId) => {
history.push(`/product/${productId}`);
}, [history]);

return (
<div className="product-list">
{sortedProducts.map(product => (
<ProductCard
key={product.id}
product={product}
onAddToCart={handleAddToCart}
onClick={handleProductClick}
/>
))}
</div>
);
}

// 状态提升与Context分割
const UserContext = createContext(null);
const ThemeContext = createContext(null);

// 分割为多个小Context
const UserIdContext = createContext(null);
const UserNameContext = createContext(null);
const UserPreferencesContext = createContext(null);

// 动态import懒加载组件
const Modal = lazy(() => import('./Modal'));
const Dropdown = lazy(() => import('./Dropdown'));

4.3 动画性能优化

// 高效动画实现
class AnimationController {
constructor() {
this.animations = new Map();
this rafId = null;
}

animate(element, keyframes, options = {}) {
const {
duration = 300,
easing = 'ease-out',
fill = 'forwards'
} = options;

// 使用Web Animations API
const animation = element.animate(keyframes, {
duration,
easing,
fill
});

return new Promise(resolve => {
animation.onfinish = resolve;
});
}

// FLIP动画技术
async flipAnimation(element, firstRect, lastRect) {
// First: 记录初始位置
const first = firstRect;

// Last: 记录最终位置(已在DOM上)
const last = element.getBoundingClientRect();

// Invert: 计算偏移
const deltaX = first.left – last.left;
const deltaY = first.top – last.top;
const deltaW = first.width / last.width;
const deltaH = first.height / last.height;

// Play: 从偏移位置动画到最终位置
element.style.transform = `translate(${deltaX}px, ${deltaY}px)`;
element.style.opacity = '0';

// 使用requestAnimationFrame确保在下一帧开始动画
requestAnimationFrame(() => {
element.style.transition = 'transform 300ms ease-out, opacity 300ms ease-out';
element.style.transform = 'translate(0, 0)';
element.style.opacity = '1';

setTimeout(() => {
element.style.transition = '';
}, 300);
});
}
}

// CSS动画优化
const styles = `
.animated-element {
/* 启用硬件加速 */
transform: translateZ(0);
will-change: transform, opacity;

/* 使用GPU合成属性 */
transform: translate3d(0, 0, 0);
perspective: 1000px;
}

/* 动画开始时才添加这个类 */
.animate-in {
animation: slideIn 300ms ease-out forwards;
}

@keyframes slideIn {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
`;

五、缓存策略

5.1 浏览器缓存

// Service Worker缓存策略
const CACHE_NAME = 'v1.0.0';
const STATIC_ASSETS = [
'/',
'/index.html',
'/static/js/main.js',
'/static/css/main.css',
'/static/images/logo.png'
];

// 安装事件 – 缓存静态资源
self.addEventListener('install', event => {
event.waitUntil(
caches.open(CACHE_NAME)
.then(cache => cache.addAll(STATIC_ASSETS))
.then(() => self.skipWaiting())
);
});

// 激活事件 – 清理旧缓存
self.addEventListener('activate', event => {
event.waitUntil(
caches.keys()
.then(cacheNames => {
return Promise.all(
cacheNames
.filter(name => name !== CACHE_NAME)
.map(name => caches.delete(name))
);
})
.then(() => self.clients.claim())
);
});

// 请求拦截 – 实现多种缓存策略
self.addEventListener('fetch', event => {
const { request } = event;
const url = new URL(request.url);

// 根据请求类型选择策略
if (request.method !== 'GET') return;

if (url.origin === location.origin) {
// 同源请求 – Cache First
event.respondWith(cacheFirst(request));
} else if (url.origin === 'https://api.example.com') {
// API请求 – Network First
event.respondWith(networkFirst(request));
} else if (url.origin.includes('cdn')) {
// CDN资源 – Stale While Revalidate
event.respondWith(staleWhileRevalidate(request));
}
});

// Cache First策略
async function cacheFirst(request) {
const cached = await caches.match(request);
if (cached) return cached;

try {
const response = await fetch(request);
if (response.ok) {
const cache = await caches.open(CACHE_NAME);
cache.put(request, response.clone());
}
return response;
} catch (error) {
return new Response('Offline', { status: 503 });
}
}

// Network First策略
async function networkFirst(request) {
try {
const response = await fetch(request);
if (response.ok) {
const cache = await caches.open(CACHE_NAME);
cache.put(request, response.clone());
}
return response;
} catch (error) {
const cached = await caches.match(request);
if (cached) return cached;
return new Response('Offline', { status: 503 });
}
}

// Stale While Revalidate策略
async function staleWhileRevalidate(request) {
const cache = await caches.open(CACHE_NAME);
const cached = await cache.match(request);

// 立即返回缓存,后台更新
const fetchPromise = fetch(request)
.then(response => {
if (response.ok) {
cache.put(request, response.clone());
}
return response;
})
.catch(() => cached);

return cached || fetchPromise;
}

5.2 HTTP缓存头

// 服务器端设置缓存头
// Express.js
const express = require('express');
const app = express();

// 静态资源 – 长期缓存
app.use('/static', express.static('public', {
maxAge: '1y',
etag: true,
lastModified: true,
setHeaders: (res, path) => {
// 对于包含hash的文件名,使用长期缓存
if (path.includes('.')) {
res.setHeader('Cache-Control', 'public, max-age=31536000, immutable');
}
}
}));

// HTML – 不缓存
app.get('*.html', (req, res) => {
res.setHeader('Cache-Control', 'no-cache, no-store, must-revalidate');
});

// API响应 – 短期缓存
app.get('/api/products', (req, res) => {
res.setHeader('Cache-Control', 'public, max-age=60, stale-while-revalidate=300');
res.json(products);
});

// 用户特定数据 – 私有缓存
app.get('/api/user/profile', (req, res) => {
res.setHeader('Cache-Control', 'private, max-age=300');
res.json(userData);
});

5.3 数据缓存

// React数据缓存Hooks
function useCachedData(key, fetcher, options = {}) {
const { staleTime = 60000, cacheTime = 300000 } = options;
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
const cache = useRef(new Map());
const fetchTime = useRef(0);

useEffect(() => {
const cached = cache.current.get(key);
const now = Date.now();

if (cached && (now – cached.timestamp) < staleTime) {
setData(cached.data);
setLoading(false);
return;
}

// 需要重新获取
setLoading(true);
fetcher()
.then(result => {
cache.current.set(key, { data: result, timestamp: Date.now() });
setData(result);
setError(null);
})
.catch(err => {
setError(err);
if (cached) {
setData(cached.data); // 使用过期缓存
}
})
.finally(() => setLoading(false));

// 清理过期缓存
const cleanup = setInterval(() => {
const now = Date.now();
for (const [k, v] of cache.current.entries()) {
if (now – v.timestamp > cacheTime) {
cache.current.delete(k);
}
}
}, 60000);

return () => clearInterval(cleanup);
}, [key, fetcher, staleTime, cacheTime]);

const invalidate = () => {
cache.current.delete(key);
};

return { data, loading, error, invalidate };
}

// 使用示例
function ProductList({ categoryId }) {
const { data, loading, error } = useCachedData(
`products-${categoryId}`,
() => fetchProducts(categoryId),
{ staleTime: 30000 }
);

// …
}

六、性能监控与诊断

6.1 Performance Observer

// 监控各种性能指标
class PerformanceMonitor {
constructor() {
this.metrics = {};
this.observers = [];
}

start() {
this.observeLongTasks();
this.observeFirstInput();
this.observeLayoutShift();
this.observePaint();
this.observeResource();
}

observeLongTasks() {
const observer = new PerformanceObserver(list => {
list.getEntries().forEach(entry => {
console.log('Long Task:', {
duration: entry.duration,
startTime: entry.startTime,
attribution: entry.attribution
});

// 上报到监控系统
this.reportMetric('longtask', {
duration: entry.duration,
startTime: entry.startTime
});
});
});

observer.observe({ type: 'longtask', buffered: true });
this.observers.push(observer);
}

observeFirstInput() {
const observer = new PerformanceObserver(list => {
list.getEntries().forEach(entry => {
console.log('First Input Delay:', {
delay: entry.processingStart – entry.startTime,
processingStart: entry.processingStart,
startTime: entry.startTime
});

this.reportMetric('fid', {
delay: entry.processingStart – entry.startTime
});
});
});

observer.observe({ type: 'first-input', buffered: true });
this.observers.push(observer);
}

observeLayoutShift() {
let clsValue = 0;

const observer = new PerformanceObserver(list => {
list.getEntries().forEach(entry => {
if (!entry.hadRecentInput) {
clsValue += entry.value;
console.log('Layout Shift:', {
value: entry.value,
sources: entry.sources
});
}
});

this.reportMetric('cls', { value: clsValue });
});

observer.observe({ type: 'layout-shift', buffered: true });
this.observers.push(observer);
}

observePaint() {
const observer = new PerformanceObserver(list => {
list.getEntries().forEach(entry => {
if (entry.name === 'first-contentful-paint') {
console.log('FCP:', entry.startTime);
this.reportMetric('fcp', { value: entry.startTime });
}
});
});

observer.observe({ type: 'paint', buffered: true });
this.observers.push(observer);
}

observeResource() {
const observer = new PerformanceObserver(list => {
list.getEntries().forEach(entry => {
const resourceData = {
name: entry.name,
type: entry.initiatorType,
duration: entry.duration,
dns: entry.domainLookupEnd – entry.domainLookupStart,
tcp: entry.connectEnd – entry.connectStart,
ttfb: entry.responseStart – entry.requestStart,
download: entry.responseEnd – entry.responseStart,
size: entry.transferSize
};

// 慢资源告警
if (entry.duration > 3000) {
console.warn('Slow resource:', resourceData);
}
});
});

observer.observe({ type: 'resource', buffered: true });
this.observers.push(observer);
}

reportMetric(name, data) {
// 发送到监控系统
fetch('/api/metrics', {
method: 'POST',
body: JSON.stringify({
name,
data,
timestamp: Date.now(),
url: location.href,
userAgent: navigator.userAgent
})
});
}

stop() {
this.observers.forEach(observer => observer.disconnect());
}
}

const monitor = new PerformanceMonitor();
monitor.start();

6.2 React Profiler

// React Profiler包装组件
const Profiler = React.Profiler;

function ProfilerWrapper({ children, id }) {
const handleRender = (
id,
phase,
actualDuration,
baseDuration,
startTime,
commitTime,
interactions
) => {
// 只在开发环境记录
if (process.env.NODE_ENV === 'development') {
console.group(`Profiler [${id}]`);
console.log('Phase:', phase);
console.log('Actual Duration:', actualDuration.toFixed(2), 'ms');
console.log('Base Duration:', baseDuration.toFixed(2), 'ms');
console.log('Commit Time:', commitTime);
console.log('Interactions:', interactions);
console.groupEnd();
}

// 生产环境上报
if (actualDuration > 100) {
reportToAnalytics({
component: id,
phase,
duration: actualDuration,
baseDuration
});
}
};

return (
<Profiler id={id} onRender={handleRender}>
{children}
</Profiler>
);
}

// 使用
function App() {
return (
<Profiler id="App" onRender={handleRender}>
<Router>
<Profiler id="Router" onRender={handleRender}>
<Routes>…</Routes>
</Profiler>
</Router>
</Profiler>
);
}

七、实战优化清单

7.1 加载优化清单

<!– Critical CSS内联 –>
<head>
<style>
/* 关键CSS – 首屏渲染所需 */
body { margin: 0; font-family: system-ui; }
.header { background: #333; color: #fff; }
.hero { min-height: 60vh; }
/* 避免FOUC */
img { opacity: 0; transition: opacity 0.3s; }
img.loaded { opacity: 1; }
</style>

<!– 预连接关键资源 –>
<link rel="preconnect" href="https://fonts.googleapis.com">

<!– 预加载关键资源 –>
<link rel="preload" href="/fonts/main.woff2" as="font" crossorigin>
</head>

<!– 结构化加载 –>
<body>
<div id="root">
<!– 服务端渲染首屏内容 –>
<header class="header">…</header>
<main class="hero">…</main>
</div>

<!– 异步加载非关键资源 –>
<script>
// 动态加载脚本
function loadScript(src) {
return new Promise((resolve, reject) => {
const script = document.createElement('script');
script.src = src;
script.async = true;
script.onload = resolve;
script.onerror = reject;
document.head.appendChild(script);
});
}

// 首屏渲染后加载
if ('requestIdleCallback' in window) {
requestIdleCallback(() => {
loadScript('/js/bundle.js');
});
} else {
setTimeout(() => {
loadScript('/js/bundle.js');
}, 1000);
}
</script>
</body>

7.2 完整优化配置

// 完整的性能优化配置示例
// next.config.js (Next.js)
module.exports = {
compiler: {
removeConsole: process.env.NODE_ENV === 'production'
},
experimental: {
optimizeCss: true
},
images: {
formats: ['image/avif', 'image/webp'],
deviceSizes: [640, 750, 828, 1080, 1200, 1920, 2048],
imageSizes: [16, 32, 48, 64, 96, 128, 256, 384],
domains: ['example.com'],
minimumCacheTTL: 60 * 60 * 24 // 24小时
},
async headers() {
return [
{
source: '/:path*',
headers: [
{
key: 'X-DNS-Prefetch-Control',
value: 'on'
},
{
key: 'X-Content-Type-Options',
value: 'nosniff'
},
{
key: 'Referrer-Policy',
value: 'origin-when-cross-origin'
}
]
},
{
source: '/static/:path*',
headers: [
{
key: 'Cache-Control',
value: 'public, max-age=31536000, immutable'
}
]
}
];
}
};

总结

前端性能优化是一个系统性的工程,需要从加载、解析、执行、渲染等多个环节综合考虑。本文详细介绍了Core Web Vitals指标、网络请求优化、JavaScript执行优化、渲染性能优化、缓存策略以及性能监控等核心内容。

关键优化点总结:

  • 减少请求体积:代码压缩、图片优化、字体子集
  • 减少请求次数:代码分割、资源合并、缓存复用
  • 优化关键路径:内联关键CSS、预加载首屏资源
  • 提升渲染性能:避免重排重绘、使用虚拟列表、React优化
  • 合理使用缓存:HTTP缓存、Service Worker、本地缓存
  • 持续监控:建立性能指标体系,及时发现回归
  • 性能优化是一个持续迭代的过程,需要在开发、测试、上线的各个环节都保持关注。希望本文能为读者的前端性能优化实践提供全面的指导。

    赞(0)
    未经允许不得转载:171主机测评 » 【前端进阶】前端性能优化完全指南:从加载到渲染
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址