每天一个高级前端知识 - Day 27
今日主题:前端性能优化的极致 - 从浏览器原理到实战技巧
核心概念:性能优化不是技巧堆砌,而是对浏览器渲染机制的深刻理解
真正的性能优化是从输入 URL 到像素绘制的全链路优化,每一毫秒的节省都需要对浏览器底层机制的精准把握。
🔬 浏览器渲染全链路
输入 URL
↓
DNS 解析 (20-120ms)
↓
TCP 连接 (三次握手 + TLS)
↓
请求响应 (TTFB)
↓
HTML 解析 → DOM 树
↓
CSS 解析 → CSSOM 树 → 渲染树
↓
布局 Layout (重排)
↓
绘制 Paint (重绘)
↓
合成 Composite
↓
屏幕显示
⚡ 网络层极致优化
// ============ 1. DNS 预解析与预连接 ============
// 在 HTML head 中添加
const addResourceHints = () => {
// DNS 预解析(提前解析第三方域名)
const dnsPrefetch = document.createElement('link');
dnsPrefetch.rel = 'dns-prefetch';
dnsPrefetch.href = 'https://api.example.com';
document.head.appendChild(dnsPrefetch);
// 预连接(DNS + TCP + TLS)
const preconnect = document.createElement('link');
preconnect.rel = 'preconnect';
preconnect.href = 'https://cdn.example.com';
preconnect.crossOrigin = 'anonymous';
document.head.appendChild(preconnect);
// 预加载关键资源
const preload = document.createElement('link');
preload.rel = 'preload';
preload.as = 'font';
preload.href = '/fonts/main.woff2';
preload.crossOrigin = 'anonymous';
document.head.appendChild(preload);
// 预获取(空闲时加载)
const prefetch = document.createElement('link');
prefetch.rel = 'prefetch';
prefetch.as = 'document';
prefetch.href = '/next-page';
document.head.appendChild(prefetch);
// 预渲染(极速跳转,谨慎使用)
const prerender = document.createElement('link');
prerender.rel = 'prerender';
prerender.href = '/likely-next-page';
document.head.appendChild(prerender);
};
// ============ 2. HTTP/2 多路复用与服务器推送 ============
// 服务器配置 (nginx)
// http2_push_preload on;
// location / {
// http2_push /css/style.css;
// http2_push /js/main.js;
// }
// ============ 3. 资源优先级调度 ============
class ResourceScheduler {
constructor() {
this.highPriority = new Set();
this.normalPriority = new Set();
this.lowPriority = new Set();
this.observer = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
this.adjustPriority(entry);
}
});
this.observer.observe({ entryTypes: ['resource'] });
}
adjustPriority(entry) {
// 首屏关键资源提高优先级
const criticalResources = ['main.js', 'vendor.js', 'style.css'];
const url = entry.name.split('/').pop();
if (criticalResources.includes(url) && entry.delay > 0) {
// 使用 Priority Hints API
if ('priority' in entry) {
entry.priority = 'high';
}
}
}
// 延迟加载非关键资源
lazyLoadResources() {
const lazyResources = document.querySelectorAll('[data-lazy]');
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const resource = entry.target;
const url = resource.dataset.src;
if (url) {
const link = document.createElement('link');
link.rel = 'preload';
link.as = resource.dataset.as || 'image';
link.href = url;
document.head.appendChild(link);
}
observer.unobserve(resource);
}
});
});
lazyResources.forEach(resource => observer.observe(resource));
}
}
🎨 渲染层极致优化
// ============ 1. 关键渲染路径优化 ============
// 内联关键 CSS
const inlineCriticalCSS = () => {
// 提取首屏所需 CSS(使用 Critical CLI)
// critical --base . --index index.html --inline --minify
};
// 延迟非关键 CSS
const loadNonCriticalCSS = () => {
const link = document.createElement('link');
link.rel = 'stylesheet';
link.href = '/css/non-critical.css';
link.media = 'print'; // 先使用 print 媒体类型,不会阻塞渲染
link.onload = () => link.media = 'all';
document.head.appendChild(link);
};
// ============ 2. 避免布局抖动 (Layout Thrashing) ============
class LayoutOptimizer {
constructor() {
this.reads = [];
this.writes = [];
this.rafId = null;
}
// 批量读取 DOM 属性
read(operation) {
this.reads.push(operation);
this.schedule();
}
// 批量写入 DOM 属性
write(operation) {
this.writes.push(operation);
this.schedule();
}
schedule() {
if (this.rafId) return;
this.rafId = requestAnimationFrame(() => {
// 先批量读取
this.reads.forEach(read => read());
this.reads = [];
// 再批量写入
this.writes.forEach(write => write());
this.writes = [];
this.rafId = null;
});
}
}
const layoutOptimizer = new LayoutOptimizer();
// ❌ 糟糕的写法 - 导致多次强制同步布局
function badResize(elements) {
elements.forEach(el => {
const width = el.offsetWidth; // 读
el.style.width = width + 10 + 'px'; // 写 → 强制布局
const height = el.offsetHeight; // 读 → 再次强制布局
el.style.height = height + 10 + 'px';
});
}
// ✅ 优化写法 - 批量读写
function goodResize(elements) {
// 先批量读取
const sizes = elements.map(el => ({
width: el.offsetWidth,
height: el.offsetHeight,
el
}));
// 再批量写入
requestAnimationFrame(() => {
sizes.forEach(({ el, width, height }) => {
el.style.width = width + 10 + 'px';
el.style.height = height + 10 + 'px';
});
});
}
// ============ 3. GPU 加速与合成层提升 ============
const gpuAcceleration = {
// 触发 GPU 合成的属性
triggers: [
'transform: translateZ(0)',
'transform: translate3d(0,0,0)',
'will-change: transform',
'will-change: opacity'
],
// 动画使用 transform 和 opacity
// ❌ 差
// element.style.left = '100px';
// ✅ 好
// element.style.transform = 'translateX(100px)';
// 合成层爆炸的预防
preventLayerExplosion() {
// 限制合成层数量,使用 contain 属性
// .list-item {
// contain: layout style paint;
// }
}
};
// ============ 4. 虚拟滚动 (处理大数据列表) ============
class VirtualScroller {
constructor(container, options) {
this.container = container;
this.itemHeight = options.itemHeight;
this.renderItem = options.renderItem;
this.totalItems = options.totalItems;
this.bufferSize = options.bufferSize || 5;
this.scrollTop = 0;
this.startIndex = 0;
this.endIndex = 0;
this.rafId = null;
this.setupDOM();
this.bindEvents();
this.render();
}
setupDOM() {
this.viewport = document.createElement('div');
this.viewport.style.height = '100%';
this.viewport.style.overflow = 'auto';
this.viewport.style.position = 'relative';
this.viewport.style.willChange = 'transform';
this.content = document.createElement('div');
this.content.style.position = 'absolute';
this.content.style.top = '0';
this.content.style.left = '0';
this.content.style.width = '100%';
this.content.style.willChange = 'transform';
this.container.appendChild(this.viewport);
this.viewport.appendChild(this.content);
// 设置总高度
const totalHeight = this.totalItems * this.itemHeight;
this.content.style.height = `${totalHeight}px`;
}
bindEvents() {
// 使用 passive 提升滚动性能
this.viewport.addEventListener('scroll', () => {
if (this.rafId) return;
this.rafId = requestAnimationFrame(() => {
this.scrollTop = this.viewport.scrollTop;
this.updateVisibleRange();
this.rafId = null;
});
}, { passive: true });
}
updateVisibleRange() {
const viewportHeight = this.viewport.clientHeight;
const start = Math.floor(this.scrollTop / this.itemHeight);
const visibleCount = Math.ceil(viewportHeight / this.itemHeight);
this.startIndex = Math.max(0, start - this.bufferSize);
this.endIndex = Math.min(
this.totalItems - 1,
start + visibleCount + this.bufferSize
);
this.render();
}
render() {
const fragment = document.createDocumentFragment();
const startOffset = this.startIndex * this.itemHeight;
for (let i = this.startIndex; i <= this.endIndex; i++) {
const item = this.renderItem(i);
item.style.position = 'absolute';
item.style.top = `${i * this.itemHeight - startOffset}px`;
item.style.left = '0';
item.style.width = '100%';
item.style.height = `${this.itemHeight}px`;
item.style.willChange = 'transform';
fragment.appendChild(item);
}
// 清理 DOM 节点,避免内存泄漏
while (this.content.firstChild) {
this.content.removeChild(this.content.firstChild);
}
this.content.appendChild(fragment);
this.content.style.transform = `translateY(${startOffset}px)`;
}
}
📦 资源加载极致优化
// ============ 1. 智能图片加载策略 ============
class ImageOptimizer {
constructor() {
this.supportsWebP = null;
this.supportsAVIF = null;
this.init();
}
async init() {
this.supportsWebP = await this.checkWebPSupport();
this.supportsAVIF = await this.checkAVIFSupport();
}
async checkWebPSupport() {
const canvas = document.createElement('canvas');
canvas.width = 1;
canvas.height = 1;
const ctx = canvas.getContext('2d');
const data = ctx.getImageData(0, 0, 1, 1);
return canvas.toDataURL('image/webp').indexOf('image/webp') === 5;
}
async checkAVIFSupport() {
return new Promise((resolve) => {
const img = new Image();
img.onload = () => resolve(true);
img.onerror = () => resolve(false);
img.src = 'data:image/avif;base64,AAAAIGZ0eXBhdmlmAAAAAGF2aWZtaWYxbWlhZk1BMUIAAADybWV0YQAAAAAAAAAoaGRscgAAAAAAAAAAcGljdAAAAAAAAAAAAAAAAGxpYmF2aWYAAAAADnBpdG0AAAAAAAEAAAAeaWxvYwAAAABEAAABAAEAAAABAAABGgAAAB0AAAAoaWluZgAAAAAAAQAAABppbmZlAgAAAAABAABhdjAxQ29sb3IAAAAAamlwcnAAAABLaXBjbwAAABRpc3BlAAAAAAAAAAIAAAACAAAAEHBpeGkAAAAAAwgICAAAAAxhdjFDgQ0MAAAAABNjb2xybmNseAACAAIAAYAAAAAXaXBtYQAAAAAAAAABAAEEAQKDBAAAACVtZGF0EgAKCBgANogQEAwgMg8f8D///8WfhwB8+ErK42A=';
});
}
getOptimalUrl(url) {
if (this.supportsAVIF) {
return url.replace(/\.(jpg|png|webp)/, '.avif');
}
if (this.supportsWebP) {
return url.replace(/\.(jpg|png)/, '.webp');
}
return url;
}
// 响应式图片加载
setupResponsiveImages() {
const images = document.querySelectorAll('img[data-src]');
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const img = entry.target;
const src = img.dataset.src;
const srcset = img.dataset.srcset;
const sizes = img.dataset.sizes;
if (srcset) {
img.srcset = srcset;
}
if (sizes) {
img.sizes = sizes;
}
img.src = src;
// 图片加载完成后添加模糊到清晰的过渡
img.style.filter = 'blur(10px)';
img.onload = () => {
img.style.transition = 'filter 0.3s';
img.style.filter = 'blur(0)';
};
observer.unobserve(img);
}
});
}, {
rootMargin: '50px', // 提前50px加载
threshold: 0.01
});
images.forEach(img => observer.observe(img));
}
// 使用新的 decoding 和 loading 属性
optimizeImageAttributes() {
document.querySelectorAll('img').forEach(img => {
img.loading = 'lazy';
img.decoding = 'async';
});
}
}
// ============ 2. 代码分割与动态导入策略 ============
const routeLoader = {
// 基于路由的代码分割
routes: {
home: () => import(/* webpackChunkName: "home" */ './pages/Home'),
about: () => import(/* webpackChunkName: "about" */ './pages/About'),
dashboard: () => import(/* webpackChunkName: "dashboard" */ './pages/Dashboard')
},
// 预加载策略
preloadRoute(routeName) {
if ('requestIdleCallback' in window) {
requestIdleCallback(() => {
this.routes[routeName]();
});
} else {
setTimeout(() => this.routes[routeName](), 1);
}
},
// 基于 hover 预加载
setupPrefetchOnHover() {
document.querySelectorAll('[data-route]').forEach(link => {
link.addEventListener('mouseenter', () => {
const route = link.dataset.route;
this.preloadRoute(route);
}, { once: true });
});
}
};
// ============ 3. 缓存策略 ============
class CacheStrategy {
// Service Worker 缓存策略
static getCacheStrategies() {
return {
// Cache First (优先缓存)
cacheFirst: async (request) => {
const cache = await caches.open('v1');
const cached = await cache.match(request);
if (cached) return cached;
const response = await fetch(request);
cache.put(request, response.clone());
return response;
},
// Network First (优先网络)
networkFirst: async (request) => {
try {
const response = await fetch(request);
const cache = await caches.open('v1');
cache.put(request, response.clone());
return response;
} catch (error) {
const cached = await caches.match(request);
return cached || new Response('Offline');
}
},
// Stale While Revalidate (后台更新)
staleWhileRevalidate: async (request) => {
const cache = await caches.open('v1');
const cached = await cache.match(request);
const fetchPromise = fetch(request).then(async (response) => {
cache.put(request, response.clone());
return response;
});
return cached || fetchPromise;
}
};
}
// 使用 IndexedDB 缓存 API 数据
async apiCache(key, fetcher, ttl = 60000) {
const cached = await this.getFromDB(key);
if (cached && Date.now() - cached.timestamp < ttl) {
return cached.data;
}
const data = await fetcher();
await this.saveToDB(key, data);
return data;
}
private async getFromDB(key) {
return new Promise((resolve) => {
const request = indexedDB.open('apiCache', 1);
request.onsuccess = () => {
const db = request.result;
const tx = db.transaction('cache', 'readonly');
const store = tx.objectStore('cache');
const getRequest = store.get(key);
getRequest.onsuccess = () => resolve(getRequest.result);
};
request.onerror = () => resolve(null);
});
}
}
🎯 今日挑战
实现一个完整的性能优化工具库,要求:
- 资源预加载管理器(支持 DNS、preconnect、preload、prefetch)
- 虚拟滚动组件(支持百万级数据)
- 图片优化器(WebP/AVIF + 懒加载 + 响应式)
- 布局抖动检测与修复工具
- 性能监控与告警(Core Web Vitals)
- Service Worker 缓存策略
// 使用示例
const perfOptimizer = new PerfOptimizer({
preload: {
dns: ['https://api.example.com'],
preconnect: ['https://cdn.example.com'],
preload: [
{ href: '/fonts/main.woff2', as: 'font' },
{ href: '/css/critical.css', as: 'style' }
]
},
images: {
lazyLoad: true,
useWebP: true,
quality: 80
},
virtualScroll: {
enabled: true,
itemHeight: 50
}
});
perfOptimizer.init();
💡 性能箴言:"最快的请求是不发送的请求,最快的资源是不加载的资源。懒加载、预加载、缓存是性能优化的三驾马车。"