前端性能优化
约 2645 字大约 9 分钟
前端性能优化
加载性能优化 🟢
1. 资源加载优化
问题: 如何优化网站的资源加载性能,提升首屏加载速度?
答案:
1.1 资源压缩
资源压缩是提升加载性能的基础手段,主要包括以下几个方面:
- 代码压缩
- JavaScript压缩:去除空格、注释、缩短变量名,减小文件体积
- CSS压缩:合并重复规则、去除无效规则,优化选择器
- HTML压缩:去除空格、注释,压缩内联脚本和样式
- 图片压缩
- 使用适当的图片格式(WebP, AVIF):新一代图片格式提供更好的压缩率
- 根据设备提供不同分辨率:避免移动端加载过大图片
- 使用图片CDN动态压缩:按需生成最适合的图片
代码示例:
// webpack压缩配置
module.exports = {
optimization: {
minimize: true, // 启用压缩
minimizer: [
new TerserPlugin({
terserOptions: {
compress: {
// 移除console语句
drop_console: true,
// 移除debugger语句
drop_debugger: true,
// 移除指定函数调用
pure_funcs: ['console.log']
},
mangle: {
// 保留特定标识符不被压缩
reserved: ['$', 'jQuery']
}
},
// 启用多进程压缩,提升构建速度
parallel: true
}),
new CssMinimizerPlugin({
minimizerOptions: {
preset: [
'default',
{
// 移除所有注释
discardComments: { removeAll: true },
// 不压缩空白符,避免某些布局问题
normalizeWhitespace: false
}
]
}
})
]
}
};
1.2 资源加载策略
合理的资源加载策略可以显著提升用户体验,主要包括:
- 预加载关键资源: 通过预加载,我们可以提前加载后续会用到的资源,加快页面响应速度。
<!-- 预加载关键CSS -->
<link rel="preload" href="critical.css" as="style">
<!-- 预加载字体,使用crossorigin避免重复加载 -->
<link rel="preload"
href="font.woff2"
as="font"
type="font/woff2"
crossorigin="anonymous">
<!-- DNS预解析,提前解析域名 -->
<link rel="dns-prefetch" href="//api.example.com">
<!-- 预连接,提前建立连接 -->
<link rel="preconnect" href="https://api.example.com">
<!-- 预渲染下一页面 -->
<link rel="prerender" href="next-page.html">
- 懒加载非关键资源: 对于非首屏内容,采用懒加载可以减少首屏加载时间。以下是一个完整的懒加载实现:
/**
* 通用资源懒加载类
* 支持图片、背景图等资源的懒加载
*/
class LazyLoader {
constructor(options = {}) {
// 配置默认参数
this.options = {
root: null, // 观察的根元素
rootMargin: '50px', // 根元素的外边距
threshold: 0.1, // 可见性阈值
...options
};
// 创建交叉观察器
this.observer = new IntersectionObserver(
this.handleIntersection.bind(this),
this.options
);
}
/**
* 开始观察元素
* @param {NodeList} elements - 需要懒加载的元素集合
*/
observe(elements) {
elements.forEach(element => {
// 只观察带有data-src属性的元素
if (element.dataset.src) {
this.observer.observe(element);
}
});
}
/**
* 处理元素进入视口的回调
* @param {IntersectionObserverEntry[]} entries - 观察条目
*/
handleIntersection(entries) {
entries.forEach(entry => {
if (entry.isIntersecting) {
this.loadElement(entry.target);
// 加载完成后停止观察
this.observer.unobserve(entry.target);
}
});
}
/**
* 加载元素资源
* @param {Element} element - 需要加载资源的元素
*/
loadElement(element) {
const src = element.dataset.src;
if (!src) return;
// 根据元素类型选择加载方式
if (element.tagName.toLowerCase() === 'img') {
this.loadImage(element, src);
} else {
this.loadBackground(element, src);
}
}
/**
* 加载图片
* @param {HTMLImageElement} img - 图片元素
* @param {string} src - 图片源地址
*/
loadImage(img, src) {
// 创建临时图片确保加载完成
const tempImage = new Image();
tempImage.onload = () => {
img.src = src;
img.removeAttribute('data-src');
img.classList.add('loaded');
};
tempImage.src = src;
}
/**
* 加载背景图
* @param {Element} element - 需要设置背景图的元素
* @param {string} src - 背景图源地址
*/
loadBackground(element, src) {
const tempImage = new Image();
tempImage.onload = () => {
element.style.backgroundImage = `url(${src})`;
element.removeAttribute('data-src');
element.classList.add('loaded');
};
tempImage.src = src;
}
}
// 使用示例
const lazyLoader = new LazyLoader();
lazyLoader.observe(document.querySelectorAll('[data-src]'));
// React组件懒加载示例
const LazyComponent = React.lazy(() => import('./LazyComponent'));
function App() {
return (
<Suspense fallback={<Loading />}>
<LazyComponent />
</Suspense>
);
}
2. 网络传输优化
问题: 如何优化网络传输性能?
答案:
2.1 HTTP优化
- HTTP/2特性利用:
- 多路复用:单个TCP连接处理多个请求
- 服务器推送:主动推送关键资源
- 头部压缩:减少传输数据量
- 缓存策略优化:
// 缓存策略实现
class CacheStrategy {
constructor() {
this.strategies = {
// 频繁变动的资源
api: {
cacheControl: 'no-cache',
etag: true,
vary: 'Accept-Encoding, Accept'
},
// 静态资源
static: {
cacheControl: 'public, max-age=31536000, immutable',
etag: false,
compression: true
},
// HTML文档
document: {
cacheControl: 'public, max-age=0, must-revalidate',
etag: true,
vary: 'Accept-Encoding'
}
};
}
apply(res, type) {
const strategy = this.strategies[type];
if (!strategy) return;
const headers = {
'Cache-Control': strategy.cacheControl
};
if (strategy.etag) {
headers.ETag = this.generateETag(res.body);
}
if (strategy.vary) {
headers.Vary = strategy.vary;
}
Object.entries(headers).forEach(([key, value]) => {
res.setHeader(key, value);
});
}
generateETag(content) {
const hash = crypto.createHash('sha256');
hash.update(content);
return `"${hash.digest('hex')}"`;
}
}
渲染性能优化 🔴
1. 关键渲染路径优化
问题:如何优化页面的首次渲染性能?
答案:
1.1 关键资源识别与优化
- 识别关键资源:
- HTML文档本身
- 渲染阻塞的CSS
- 首屏JavaScript
- 首屏图片资源
- 优化策略:
// 1. 内联关键CSS
function inlineCriticalCSS() {
const criticalCSS = extractCriticalCSS();
return `
<style>
${criticalCSS}
</style>
<link rel="preload"
href="non-critical.css"
as="style"
onload="this.rel='stylesheet'">
`;
}
// 2. 延迟加载非关键JavaScript
function deferNonCriticalJS() {
return `
<script>
// 关键JS内联
${criticalJS}
</script>
<script defer src="non-critical.js"></script>
`;
}
// 3. 图片懒加载
class ImageLoader {
constructor() {
this.observer = new IntersectionObserver(
this.handleIntersection.bind(this),
{
rootMargin: '50px 0px',
threshold: 0.01
}
);
}
observe(images) {
images.forEach(img => {
if (img.dataset.src) {
this.observer.observe(img);
}
});
}
handleIntersection(entries) {
entries.forEach(entry => {
if (entry.isIntersecting) {
this.loadImage(entry.target);
this.observer.unobserve(entry.target);
}
});
}
loadImage(img) {
const src = img.dataset.src;
if (!src) return;
// 预加载图片
const tempImage = new Image();
tempImage.onload = () => {
img.src = src;
img.classList.add('loaded');
img.removeAttribute('data-src');
};
tempImage.src = src;
}
}
1.2 资源优先级控制
问题:如何合理控制资源加载优先级?
答案:
- 使用资源提示:
<!-- 预加载关键资源 -->
<link rel="preload"
href="critical-font.woff2"
as="font"
type="font/woff2"
crossorigin>
<!-- DNS预解析 -->
<link rel="dns-prefetch"
href="//api.example.com">
<!-- 预连接 -->
<link rel="preconnect"
href="https://api.example.com">
<!-- 预渲染 -->
<link rel="prerender"
href="next-page.html">
- 资源加载优先级管理:
class ResourcePriorityManager {
constructor() {
this.resourceQueue = new Map();
this.loadingResources = new Set();
this.maxConcurrent = 6;
}
addResource(url, priority = 0) {
if (!this.resourceQueue.has(priority)) {
this.resourceQueue.set(priority, []);
}
this.resourceQueue.get(priority).push(url);
this.processQueue();
}
async processQueue() {
if (this.loadingResources.size >= this.maxConcurrent) {
return;
}
const priorities = Array.from(this.resourceQueue.keys())
.sort((a, b) => b - a);
for (const priority of priorities) {
const resources = this.resourceQueue.get(priority);
while (resources.length > 0 &&
this.loadingResources.size < this.maxConcurrent) {
const url = resources.shift();
await this.loadResource(url);
}
}
}
async loadResource(url) {
this.loadingResources.add(url);
try {
if (url.endsWith('.js')) {
await this.loadScript(url);
} else if (url.endsWith('.css')) {
await this.loadStyle(url);
} else {
await this.loadGenericResource(url);
}
} finally {
this.loadingResources.delete(url);
this.processQueue();
}
}
loadScript(url) {
return new Promise((resolve, reject) => {
const script = document.createElement('script');
script.src = url;
script.async = true;
script.onload = resolve;
script.onerror = reject;
document.head.appendChild(script);
});
}
loadStyle(url) {
return new Promise((resolve, reject) => {
const link = document.createElement('link');
link.rel = 'stylesheet';
link.href = url;
link.onload = resolve;
link.onerror = reject;
document.head.appendChild(link);
});
}
loadGenericResource(url) {
return fetch(url);
}
}
2. 渲染性能优化
问题:如何优化页面渲染性能?
答案:
2.1 避免布局抖动
- 实现平滑的动画效果:
class SmoothAnimation {
constructor(element) {
this.element = element;
this.positions = new WeakMap();
this.animations = new Set();
}
animate(properties, duration = 300, easing = 'ease-out') {
// 批量读取
const currentStyles = window.getComputedStyle(this.element);
const startPos = {};
Object.keys(properties).forEach(prop => {
startPos[prop] = parseFloat(currentStyles[prop]) || 0;
});
// 批量写入
requestAnimationFrame(() => {
const animation = this.element.animate([
startPos,
properties
], {
duration,
easing,
fill: 'forwards'
});
this.animations.add(animation);
animation.onfinish = () => {
this.animations.delete(animation);
Object.assign(this.element.style, properties);
};
});
}
// 防止布局抖动的尺寸计算
measureLayout() {
if (!this.positions.has(this.element)) {
const rect = this.element.getBoundingClientRect();
this.positions.set(this.element, {
width: rect.width,
height: rect.height,
top: rect.top,
left: rect.left
});
}
return this.positions.get(this.element);
}
// 批量更新布局
updateLayout(updates) {
// 读取阶段
const measurements = new Map();
updates.forEach(update => {
const { element } = update;
if (!measurements.has(element)) {
measurements.set(element, element.getBoundingClientRect());
}
});
// 写入阶段
requestAnimationFrame(() => {
updates.forEach(update => {
const { element, changes } = update;
const measurement = measurements.get(element);
Object.entries(changes).forEach(([prop, value]) => {
if (typeof value === 'function') {
element.style[prop] = value(measurement);
} else {
element.style[prop] = value;
}
});
});
});
}
}
2.2 优化重排和重绘
- 使用CSS transform代替位置改变:
class LayoutOptimizer {
constructor() {
this.mutationQueue = new Map();
this.scheduledFrame = null;
}
// 批量处理DOM修改
batchUpdate(element, updates) {
if (!this.mutationQueue.has(element)) {
this.mutationQueue.set(element, new Set());
}
updates.forEach(update => {
this.mutationQueue.get(element).add(update);
});
if (!this.scheduledFrame) {
this.scheduledFrame = requestAnimationFrame(() => {
this.flushMutations();
});
}
}
// 执行批量更新
flushMutations() {
for (const [element, updates] of this.mutationQueue) {
// 使用transform优化
const transform = {
translate: { x: 0, y: 0 },
scale: { x: 1, y: 1 },
rotate: 0
};
updates.forEach(update => {
switch (update.type) {
case 'move':
transform.translate.x += update.x;
transform.translate.y += update.y;
break;
case 'scale':
transform.scale.x *= update.x;
transform.scale.y *= update.y;
break;
case 'rotate':
transform.rotate += update.angle;
break;
}
});
// 应用transform
element.style.transform = `
translate(${transform.translate.x}px, ${transform.translate.y}px)
scale(${transform.scale.x}, ${transform.scale.y})
rotate(${transform.rotate}deg)
`;
}
this.mutationQueue.clear();
this.scheduledFrame = null;
}
// 创建新的图层
createLayer(element) {
element.style.willChange = 'transform';
element.style.backfaceVisibility = 'hidden';
element.style.perspective = '1000px';
return element;
}
}
性能监控与分析 🔴
1. 性能指标采集
问题:如何全面监控前端性能?
答案:
1.1 核心性能指标
- 加载性能指标:
- FP (First Paint): 首次绘制时间
- FCP (First Contentful Paint): 首次内容绘制时间
- LCP (Largest Contentful Paint): 最大内容绘制时间
- TTI (Time to Interactive): 可交互时间
- FID (First Input Delay): 首次输入延迟
- CLS (Cumulative Layout Shift): 累积布局偏移
- 实现示例:
class PerformanceMonitor {
constructor() {
// 初始化性能指标收集器
this.metrics = {
FP: 0,
FCP: 0,
LCP: 0,
TTI: 0,
FID: 0,
CLS: 0
};
// 初始化观察器
this.initObservers();
}
initObservers() {
// FP和FCP监控
this.paintObserver = new PerformanceObserver((entries) => {
entries.getEntries().forEach(entry => {
const metricName = entry.name;
const time = Math.round(entry.startTime);
this.metrics[metricName] = time;
this.reportMetric(metricName, time);
});
});
this.paintObserver.observe({ entryTypes: ['paint'] });
// LCP监控
this.lcpObserver = new PerformanceObserver((entries) => {
const lastEntry = entries.getEntries().pop();
if (lastEntry) {
const time = Math.round(lastEntry.startTime);
this.metrics.LCP = time;
this.reportMetric('LCP', time);
}
});
this.lcpObserver.observe({ entryTypes: ['largest-contentful-paint'] });
// FID监控
this.fidObserver = new PerformanceObserver((entries) => {
entries.getEntries().forEach(entry => {
const time = Math.round(entry.processingStart - entry.startTime);
this.metrics.FID = time;
this.reportMetric('FID', time);
});
});
this.fidObserver.observe({ entryTypes: ['first-input'] });
// CLS监控
let clsValue = 0;
let clsEntries = [];
this.clsObserver = new PerformanceObserver((entries) => {
entries.getEntries().forEach(entry => {
if (!entry.hadRecentInput) {
clsValue += entry.value;
clsEntries.push(entry);
this.metrics.CLS = clsValue;
this.reportMetric('CLS', clsValue);
}
});
});
this.clsObserver.observe({ entryTypes: ['layout-shift'] });
}
// 上报性能指标
reportMetric(name, value) {
// 可以使用 sendBeacon 或 XMLHttpRequest 上报数据
const data = {
name,
value,
timestamp: Date.now(),
page: window.location.href,
connection: navigator.connection?.effectiveType || '',
device: {
userAgent: navigator.userAgent,
memory: navigator.deviceMemory,
hardwareConcurrency: navigator.hardwareConcurrency
}
};
if (navigator.sendBeacon) {
navigator.sendBeacon('/api/metrics', JSON.stringify(data));
} else {
fetch('/api/metrics', {
method: 'POST',
body: JSON.stringify(data),
keepalive: true
});
}
}
// 获取所有性能指标
getMetrics() {
return {
...this.metrics,
// 添加其他浏览器性能指标
timing: this.getNavigationTiming(),
memory: this.getMemoryInfo(),
resources: this.getResourceTiming()
};
}
// 获取导航计时信息
getNavigationTiming() {
const timing = performance.getEntriesByType('navigation')[0];
return {
dnsLookup: timing.domainLookupEnd - timing.domainLookupStart,
tcpConnect: timing.connectEnd - timing.connectStart,
sslNegotiation: timing.secureConnectionStart ?
timing.connectEnd - timing.secureConnectionStart : 0,
ttfb: timing.responseStart - timing.requestStart,
domInteractive: timing.domInteractive - timing.fetchStart,
domComplete: timing.domComplete - timing.fetchStart,
loadComplete: timing.loadEventEnd - timing.fetchStart
};
}
// 获取内存使用信息
getMemoryInfo() {
if (performance.memory) {
return {
jsHeapSizeLimit: performance.memory.jsHeapSizeLimit,
totalJSHeapSize: performance.memory.totalJSHeapSize,
usedJSHeapSize: performance.memory.usedJSHeapSize
};
}
return null;
}
// 获取资源加载计时
getResourceTiming() {
return performance.getEntriesByType('resource').map(entry => ({
name: entry.name,
type: entry.initiatorType,
duration: entry.duration,
size: entry.transferSize,
startTime: entry.startTime
}));
}
}
1.2 错误监控
问题:如何实现前端错误监控?
答案:
- 错误类型:
- JavaScript运行时错误
- Promise未捕获错误
- 资源加载错误
- API请求错误
- Vue/React等框架错误
- 实现示例:
class ErrorMonitor {
constructor(options = {}) {
this.options = {
maxErrors: 100, // 最大错误数
sampling: 1, // 采样率
ignoreErrors: [], // 忽略的错误
...options
};
this.errorCount = 0;
this.initErrorHandlers();
}
initErrorHandlers() {
// 全局错误处理
window.onerror = (message, source, lineno, colno, error) => {
this.handleError({
type: 'js',
subType: 'runtime',
message,
source,
lineno,
colno,
error
});
};
// Promise错误处理
window.addEventListener('unhandledrejection', (event) => {
this.handleError({
type: 'promise',
message: event.reason?.message || event.reason,
error: event.reason
});
});
// 资源加载错误
window.addEventListener('error', (event) => {
if (event.target && (event.target.src || event.target.href)) {
this.handleError({
type: 'resource',
subType: event.target.tagName.toLowerCase(),
url: event.target.src || event.target.href,
html: event.target.outerHTML
});
}
}, true);
// API请求错误
this.patchXHR();
this.patchFetch();
}
// 处理错误
handleError(error) {
if (this.shouldIgnore(error)) return;
if (this.errorCount >= this.options.maxErrors) return;
if (Math.random() > this.options.sampling) return;
this.errorCount++;
const errorInfo = {
...error,
timestamp: Date.now(),
page: location.href,
userAgent: navigator.userAgent,
// 获取错误发生时的性能指标
performance: this.getPerformanceMetrics()
};
this.reportError(errorInfo);
}
// 是否忽略错误
shouldIgnore(error) {
return this.options.ignoreErrors.some(pattern => {
if (pattern instanceof RegExp) {
return pattern.test(error.message);
}
return error.message?.includes(pattern);
});
}
// 获取性能指标
getPerformanceMetrics() {
const navigation = performance.getEntriesByType('navigation')[0];
return {
pageLoadTime: navigation?.loadEventEnd,
domContentLoaded: navigation?.domContentLoadedEventEnd,
firstPaint: performance.getEntriesByName('first-paint')[0]?.startTime,
firstContentfulPaint: performance.getEntriesByName('first-contentful-paint')[0]?.startTime
};
}
// 上报错误
reportError(error) {
// 可以使用 sendBeacon 或 XMLHttpRequest 上报数据
if (navigator.sendBeacon) {
navigator.sendBeacon('/api/errors', JSON.stringify(error));
} else {
fetch('/api/errors', {
method: 'POST',
body: JSON.stringify(error),
keepalive: true
});
}
}
// 拦截XHR请求
patchXHR() {
const originalXHR = window.XMLHttpRequest;
const self = this;
window.XMLHttpRequest = function() {
const xhr = new originalXHR();
const originalOpen = xhr.open;
const originalSend = xhr.send;
xhr.open = function(...args) {
this._url = args[1];
return originalOpen.apply(this, args);
};
xhr.send = function(...args) {
this.addEventListener('error', () => {
self.handleError({
type: 'xhr',
method: this._method,
url: this._url,
status: this.status,
statusText: this.statusText
});
});
return originalSend.apply(this, args);
};
return xhr;
};
}
// 拦截Fetch请求
patchFetch() {
const originalFetch = window.fetch;
const self = this;
window.fetch = function(...args) {
return originalFetch.apply(this, args)
.catch(error => {
self.handleError({
type: 'fetch',
url: args[0],
method: args[1]?.method || 'GET',
error
});
throw error;
});
};
}
}
[未完待续...]