性能优化
一、资源加载优化
1. 图片优化
图片格式选择:
- WebP:现代浏览器支持,压缩率高
- AVIF:最新格式,压缩率最高
- PNG:透明背景
- JPEG:照片
- SVG:矢量图,图标
图片优化策略:
html
<!-- 响应式图片 -->
<picture>
<source srcset="image.webp" type="image/webp">
<source srcset="image.avif" type="image/avif">
<img src="image.jpg" alt="图片">
</picture>
<!-- 懒加载 -->
<img src="image.jpg" loading="lazy" alt="图片">
<!-- 压缩图片 -->
<!-- 使用工具:TinyPNG、ImageOptim、Squoosh -->图片懒加载:
javascript
// Intersection Observer API
const images = document.querySelectorAll('img[data-src]');
const imageObserver = new IntersectionObserver((entries, observer) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const img = entry.target;
img.src = img.dataset.src;
img.removeAttribute('data-src');
observer.unobserve(img);
}
});
});
images.forEach(img => imageObserver.observe(img));2. 脚本优化
代码分割:
javascript
// 动态导入
const module = await import('./module.js');
// React 懒加载
const LazyComponent = React.lazy(() => import('./LazyComponent'));
// Vue 懒加载
const AsyncComponent = () => import('./AsyncComponent.vue');Tree Shaking:
- 移除未使用的代码
- 使用 ES6 模块
- 配置构建工具(Webpack、Rollup)
压缩和混淆:
- 使用 UglifyJS、Terser
- 启用 Gzip/Brotli 压缩
异步加载脚本:
html
<!-- defer:DOM 解析完成后执行 -->
<script src="app.js" defer></script>
<!-- async:下载完成后立即执行(不阻塞) -->
<script src="analytics.js" async></script>
<!-- 动态加载 -->
<script>
const script = document.createElement('script');
script.src = 'app.js';
document.body.appendChild(script);
</script>3. CSS 优化
关键 CSS 内联:
html
<style>
/* 首屏关键样式 */
.header { ... }
.hero { ... }
</style>
<link rel="stylesheet" href="non-critical.css">CSS 优化策略:
- 避免使用 @import(阻塞渲染)
- 压缩 CSS
- 移除未使用的 CSS
- 使用 CSS-in-JS 按需加载
CSS 加载优化:
html
<!-- 预加载 -->
<link rel="preload" href="styles.css" as="style">
<!-- 媒体查询 -->
<link rel="stylesheet" href="print.css" media="print">4. 字体优化
字体加载策略:
html
<!-- 字体预加载 -->
<link rel="preload" href="font.woff2" as="font" type="font/woff2" crossorigin>
<!-- 字体显示策略 -->
@font-face {
font-family: 'MyFont';
src: url('font.woff2') format('woff2');
font-display: swap; /* swap | block | fallback | optional */
}
<!-- 字体子集化 -->
<!-- 只包含需要的字符 -->二、渲染优化
1. 减少重排和重绘
批量 DOM 操作:
javascript
// 不推荐:多次重排
element.style.left = '10px';
element.style.top = '10px';
element.style.width = '100px';
// 推荐:一次重排
element.style.cssText = 'left: 10px; top: 10px; width: 100px;';
// 或使用 class
element.className = 'new-style';
// 或使用 DocumentFragment
const fragment = document.createDocumentFragment();
for (let i = 0; i < 1000; i++) {
const li = document.createElement('li');
fragment.appendChild(li);
}
ul.appendChild(fragment);使用 transform 代替位置属性:
javascript
// 不推荐:触发重排
element.style.left = '100px';
element.style.top = '100px';
// 推荐:使用 transform(GPU 加速)
element.style.transform = 'translate(100px, 100px)';避免频繁读取布局属性:
javascript
// 不推荐:强制同步布局
for (let i = 0; i < elements.length; i++) {
elements[i].style.width = elements[i].offsetWidth + 'px';
}
// 推荐:先读取,再写入
const widths = elements.map(el => el.offsetWidth);
elements.forEach((el, i) => {
el.style.width = widths[i] + 'px';
});2. 虚拟滚动
长列表优化:
javascript
// 虚拟滚动实现(简化版)
class VirtualScroll {
constructor(container, items, itemHeight) {
this.container = container;
this.items = items;
this.itemHeight = itemHeight;
this.visibleCount = Math.ceil(container.clientHeight / itemHeight);
this.startIndex = 0;
this.render();
this.container.addEventListener('scroll', this.handleScroll.bind(this));
}
render() {
const endIndex = Math.min(
this.startIndex + this.visibleCount + 1,
this.items.length
);
const visibleItems = this.items.slice(this.startIndex, endIndex);
this.container.innerHTML = visibleItems
.map((item, index) => `<div>${item}</div>`)
.join('');
this.container.style.paddingTop = this.startIndex * this.itemHeight + 'px';
}
handleScroll() {
const scrollTop = this.container.scrollTop;
const newStartIndex = Math.floor(scrollTop / this.itemHeight);
if (newStartIndex !== this.startIndex) {
this.startIndex = newStartIndex;
this.render();
}
}
}3. 防抖和节流
防抖(Debounce):
javascript
function debounce(func, delay) {
let timer;
return function(...args) {
clearTimeout(timer);
timer = setTimeout(() => func.apply(this, args), delay);
};
}
// 使用
const handleSearch = debounce((query) => {
search(query);
}, 300);
input.addEventListener('input', (e) => {
handleSearch(e.target.value);
});节流(Throttle):
javascript
function throttle(func, delay) {
let lastTime = 0;
return function(...args) {
const now = Date.now();
if (now - lastTime >= delay) {
func.apply(this, args);
lastTime = now;
}
};
}
// 使用
const handleScroll = throttle(() => {
updateScrollPosition();
}, 100);
window.addEventListener('scroll', handleScroll);4. 使用 requestAnimationFrame
优化动画:
javascript
function animate() {
// 动画逻辑
element.style.transform = `translateX(${x}px)`;
x += 1;
if (x < 1000) {
requestAnimationFrame(animate);
}
}
requestAnimationFrame(animate);批量 DOM 更新:
javascript
function batchUpdate() {
// 在下一帧执行,避免频繁重排
requestAnimationFrame(() => {
element1.style.width = '100px';
element2.style.height = '200px';
element3.style.left = '50px';
});
}5. 使用 will-change
提示浏览器优化:
css
.element {
will-change: transform;
transition: transform 0.3s;
}
.element:hover {
transform: translateX(100px);
}注意事项:
- 不要过度使用
- 使用后记得移除
- 只对需要动画的元素使用
javascript
element.style.willChange = 'transform';
element.addEventListener('transitionend', () => {
element.style.willChange = 'auto';
});三、网络优化
1. 缓存策略
浏览器缓存:
http
# 强缓存
Cache-Control: max-age=3600
Expires: Wed, 21 Oct 2025 07:28:00 GMT
# 协商缓存
ETag: "33a64df551425fcc55e4d42a148795d9f25f89d4"
Last-Modified: Wed, 21 Oct 2015 07:28:00 GMTService Worker 缓存:
javascript
// 注册 Service Worker
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('/sw.js');
}
// sw.js
self.addEventListener('fetch', event => {
event.respondWith(
caches.match(event.request).then(response => {
return response || fetch(event.request);
})
);
});2. HTTP/2
HTTP/2 优势:
- 多路复用:一个连接处理多个请求
- 服务器推送:主动推送资源
- 头部压缩:减少头部大小
- 二进制分帧:更高效
3. CDN
使用 CDN 加速:
- 减少延迟
- 减轻服务器压力
- 提高可用性
html
<!-- 使用 CDN -->
<script src="https://cdn.example.com/library.js"></script>
<link rel="stylesheet" href="https://cdn.example.com/style.css">4. 预加载和预渲染
预加载关键资源:
html
<!-- 预加载 -->
<link rel="preload" href="font.woff2" as="font" type="font/woff2" crossorigin>
<link rel="preload" href="critical.js" as="script">
<link rel="preload" href="critical.css" as="style">
<!-- DNS 预解析 -->
<link rel="dns-prefetch" href="https://api.example.com">
<!-- 预连接 -->
<link rel="preconnect" href="https://api.example.com">
<!-- 预渲染 -->
<link rel="prerender" href="https://example.com/page">5. 数据压缩
Gzip / Brotli:
nginx
# Nginx 配置
gzip on;
gzip_types text/plain text/css application/json application/javascript;
gzip_comp_level 6;
# Brotli(压缩率更高)
brotli on;
brotli_types text/plain text/css application/json application/javascript;四、JavaScript 优化
1. 避免内存泄漏
及时清理:
javascript
// 清理定时器
const timer = setInterval(() => {}, 1000);
clearInterval(timer);
// 移除事件监听器
element.addEventListener('click', handleClick);
element.removeEventListener('click', handleClick);
// 清理 DOM 引用
const element = document.getElementById('myElement');
// 使用完后
element = null;
// 清理闭包
function outer() {
const data = new Array(1000000);
return function inner() {
// 使用 data
};
}
// 使用完后
const inner = outer();
inner = null;2. 使用 Web Workers
将计算密集型任务移到 Worker:
javascript
// main.js
const worker = new Worker('worker.js');
worker.postMessage({ data: largeArray });
worker.onmessage = (e) => {
console.log('结果:', e.data);
};
// worker.js
self.onmessage = (e) => {
const result = heavyComputation(e.data.data);
self.postMessage(result);
};3. 使用 requestIdleCallback
处理非关键任务:
javascript
function processQueue() {
// 处理队列
}
requestIdleCallback((deadline) => {
while (deadline.timeRemaining() > 0 && tasks.length > 0) {
processQueue();
}
if (tasks.length > 0) {
requestIdleCallback(processQueue);
}
});4. 代码优化
减少函数调用:
javascript
// 不推荐:多次访问对象属性
for (let i = 0; i < array.length; i++) {
if (array[i].value > threshold) {
// ...
}
}
// 推荐:缓存对象属性
const length = array.length;
for (let i = 0; i < length; i++) {
const item = array[i];
if (item.value > threshold) {
// ...
}
}使用合适的算法和数据结构:
javascript
// 不推荐:O(n²) 复杂度
function findDuplicates(array) {
const duplicates = [];
for (let i = 0; i < array.length; i++) {
for (let j = i + 1; j < array.length; j++) {
if (array[i] === array[j]) {
duplicates.push(array[i]);
}
}
}
return duplicates;
}
// 推荐:O(n) 复杂度
function findDuplicates(array) {
const seen = new Set();
const duplicates = [];
for (const item of array) {
if (seen.has(item)) {
duplicates.push(item);
} else {
seen.add(item);
}
}
return duplicates;
}五、监控和调试
1. 性能监控
Web Vitals:
javascript
import { getCLS, getFID, getFCP, getLCP, getTTFB } from 'web-vitals';
getCLS(console.log);
getFID(console.log);
getFCP(console.log);
getLCP(console.log);
getTTFB(console.log);Performance API:
javascript
// 测量执行时间
performance.mark('start');
// 执行代码
performance.mark('end');
performance.measure('duration', 'start', 'end');
const measure = performance.getEntriesByName('duration')[0];
console.log('执行时间:', measure.duration);Chrome DevTools:
- Performance 面板:分析性能瓶颈
- Memory 面板:分析内存使用
- Network 面板:分析网络请求
2. 性能指标
关键指标:
- FCP(First Contentful Paint):首次内容绘制
- LCP(Largest Contentful Paint):最大内容绘制
- FID(First Input Delay):首次输入延迟
- CLS(Cumulative Layout Shift):累积布局偏移
- TTFB(Time to First Byte):首字节时间
六、常见面试题
1. 如何优化首屏加载时间?
- 代码分割和懒加载
- 内联关键 CSS
- 图片优化和懒加载
- 使用 CDN
- 启用 Gzip/Brotli
- 预加载关键资源
- 减少 JavaScript 执行时间
2. 如何减少重排和重绘?
- 批量 DOM 操作
- 使用 transform 代替位置属性
- 避免频繁读取布局属性
- 使用 DocumentFragment
- 使用 visibility 代替 display: none
3. 如何优化长列表性能?
- 虚拟滚动
- 分页加载
- 使用 shouldComponentUpdate(React)
- 使用 Object.freeze 冻结数据
- 避免在 render 中创建函数
4. 防抖和节流的区别?
- 防抖:事件触发后等待 n 秒再执行,期间再次触发则重新计时
- 节流:固定时间间隔内只执行一次
5. 如何监控页面性能?
- 使用 Performance API
- 使用 Web Vitals
- 使用 Chrome DevTools
- 使用第三方监控工具(如 Sentry)