03-性能优化
📋 学习目标
掌握性能分析方法
学习代码层面优化
理解资源加载优化
掌握运行时性能优化
📊 性能指标
Core Web Vitals
// LCP (Largest Contentful Paint) - 最大内容绘制
// 目标:< 2.5s
// FID (First Input Delay) - 首次输入延迟
// 目标:< 100ms
// CLS (Cumulative Layout Shift) - 累计布局偏移
// 目标:< 0.1
// 测量性能
const observer = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
console.log('LCP:', entry.renderTime || entry.loadTime);
}
});
observer.observe({entryTypes: ['largest-contentful-paint']});
性能API
// Navigation Timing
const perfData = performance.getEntriesByType('navigation')[0];
console.log('DOM加载时间:', perfData.domContentLoadedEventEnd - perfData.domContentLoadedEventStart);
console.log('页面加载时间:', perfData.loadEventEnd - perfData.loadEventStart);
// Resource Timing
const resources = performance.getEntriesByType('resource');
resources.forEach(resource => {
console.log(`${resource.name}: ${resource.duration}ms`);
});
// 自定义标记
performance.mark('start');
// 执行代码
performance.mark('end');
performance.measure('operation', 'start', 'end');
🎨 代码优化
JavaScript优化
// ❌ 避免:频繁操作DOM
for (let i = 0; i < 1000; i++) {
document.body.innerHTML += `<div>${i}</div>`;
}
// ✅ 推荐:批量操作
const fragment = document.createDocumentFragment();
for (let i = 0; i < 1000; i++) {
const div = document.createElement('div');
div.textContent = i;
fragment.appendChild(div);
}
document.body.appendChild(fragment);
// ❌ 避免:在循环中查询DOM
for (let i = 0; i < items.length; i++) {
document.getElementById('container').appendChild(items[i]);
}
// ✅ 推荐:缓存DOM引用
const container = document.getElementById('container');
for (let i = 0; i < items.length; i++) {
container.appendChild(items[i]);
}
// ❌ 避免:强制同步布局
const width = element.offsetWidth;
element.style.width = width + 10 + 'px';
const height = element.offsetHeight; // 触发回流
// ✅ 推荐:批量读写
const width = element.offsetWidth;
const height = element.offsetHeight;
element.style.width = width + 10 + 'px';
防抖和节流
// 防抖(Debounce)
function debounce(fn, delay) {
let timer = null;
return function(...args) {
if (timer) clearTimeout(timer);
timer = setTimeout(() => {
fn.apply(this, args);
}, delay);
};
}
// 使用
const handleSearch = debounce((query) => {
console.log('Searching:', query);
}, 500);
input.addEventListener('input', (e) => {
handleSearch(e.target.value);
});
// 节流(Throttle)
function throttle(fn, delay) {
let lastCall = 0;
return function(...args) {
const now = Date.now();
if (now - lastCall >= delay) {
lastCall = now;
fn.apply(this, args);
}
};
}
// 使用
const handleScroll = throttle(() => {
console.log('Scrolled');
}, 200);
window.addEventListener('scroll', handleScroll);
虚拟滚动
import {FixedSizeList} from 'react-window';
function VirtualList({items}) {
const Row = ({index, style}) => (
<div style={style}>
{items[index].name}
</div>
);
return (
<FixedSizeList
height={600}
itemCount={items.length}
itemSize={35}
width="100%"
>
{Row}
</FixedSizeList>
);
}
⚛️ React性能优化
memo和useMemo
// React.memo:避免不必要的重渲染
const ExpensiveComponent = React.memo(({data}) => {
console.log('Rendering ExpensiveComponent');
return <div>{data}</div>;
});
// useMemo:缓存计算结果
function Component({items}) {
const expensiveValue = useMemo(() => {
return items.reduce((sum, item) => sum + item.value, 0);
}, [items]);
return <div>{expensiveValue}</div>;
}
// useCallback:缓存函数
function Parent() {
const [count, setCount] = useState(0);
const handleClick = useCallback(() => {
console.log('Clicked');
}, []);
return <Child onClick={handleClick} />;
}
const Child = React.memo(({onClick}) => {
console.log('Child rendered');
return <button onClick={onClick}>Click</button>;
});
代码分割
// 路由级别分割
const Home = lazy(() => import('./pages/Home'));
const About = lazy(() => import('./pages/About'));
function App() {
return (
<Suspense fallback={<Loading />}>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
</Routes>
</Suspense>
);
}
// 组件级别分割
const HeavyComponent = lazy(() => import('./HeavyComponent'));
function Page() {
const [show, setShow] = useState(false);
return (
<div>
<button onClick={() => setShow(true)}>Show</button>
{show && (
<Suspense fallback={<div>Loading...</div>}>
<HeavyComponent />
</Suspense>
)}
</div>
);
}
列表优化
// ❌ 避免:使用index作为key
{items.map((item, index) => (
<Item key={index} data={item} />
))}
// ✅ 推荐:使用唯一ID
{items.map(item => (
<Item key={item.id} data={item} />
))}
// 虚拟化长列表
import {Virtuoso} from 'react-virtuoso';
function LongList({items}) {
return (
<Virtuoso
style={{height: '600px'}}
data={items}
itemContent={(index, item) => (
<div>{item.name}</div>
)}
/>
);
}
💚 Vue性能优化
v-memo和v-once
<template>
<!-- v-once:只渲染一次 -->
<div v-once>
{{ staticContent }}
</div>
<!-- v-memo:条件性跳过更新 -->
<div v-memo="[user.id, user.name]">
{{ user.name }} - {{ user.email }}
</div>
</template>
计算属性缓存
<script setup>
import {ref, computed} from 'vue';
const items = ref([...]);
// ✅ 使用computed(有缓存)
const filteredItems = computed(() => {
console.log('Computing...');
return items.value.filter(item => item.active);
});
// ❌ 避免:使用method(无缓存)
function getFilteredItems() {
console.log('Computing...');
return items.value.filter(item => item.active);
}
</script>
异步组件
<script setup>
import {defineAsyncComponent} from 'vue';
const AsyncComponent = defineAsyncComponent({
loader: () => import('./HeavyComponent.vue'),
loadingComponent: LoadingComponent,
delay: 200,
timeout: 3000
});
</script>
<template>
<Suspense>
<AsyncComponent />
<template v-slot:fallback>
<Loading />
</template>
</Suspense>
</template>
📦 资源优化
图片优化
<!-- 响应式图片 -->
<picture>
<source srcset="image.webp" type="image/webp">
<source srcset="image.jpg" type="image/jpeg">
<img src="image.jpg" alt="Description">
</picture>
<!-- 懒加载 -->
<img src="image.jpg" loading="lazy" alt="Description">
<!-- srcset和sizes -->
<img
srcset="small.jpg 480w, medium.jpg 800w, large.jpg 1200w"
sizes="(max-width: 600px) 480px, (max-width: 900px) 800px, 1200px"
src="medium.jpg"
alt="Responsive image"
>
字体优化
/* 字体预加载 */
<link rel="preload" href="font.woff2" as="font" type="font/woff2" crossorigin>
/* font-display */
@font-face {
font-family: 'CustomFont';
src: url('font.woff2') format('woff2');
font-display: swap; /* swap, fallback, optional */
}
/* 子集化 */
@font-face {
font-family: 'CustomFont';
src: url('font-subset.woff2') format('woff2');
unicode-range: U+0000-00FF; /* 拉丁字符 */
}
CSS优化
/* 避免昂贵的选择器 */
/* ❌ 避免 */
/* 通用选择器 */
* {
margin: 0;
}
/* 深层嵌套 */
div div div div {
color: red;
}
/* 属性选择器 */
[type="text"] {
border: 1px solid;
}
/* ✅ 推荐 */
.specific-class {
color: blue;
}
/* 使用contain */
.component {
contain: layout style paint;
}
/* 使用will-change(谨慎使用) */
.element {
will-change: transform;
}
.element:hover {
transform: scale(1.1);
}
🌐 网络优化
资源预加载
<!-- DNS预解析 -->
<link rel="dns-prefetch" href="https://api.example.com">
<!-- 预连接 -->
<link rel="preconnect" href="https://fonts.googleapis.com">
<!-- 预加载关键资源 -->
<link rel="preload" href="critical.css" as="style">
<link rel="preload" href="critical.js" as="script">
<!-- 预获取下一页资源 -->
<link rel="prefetch" href="next-page.js">
<!-- 预渲染 -->
<link rel="prerender" href="next-page.html">
代码分割
// Vite配置
export default {
build: {
rollupOptions: {
output: {
manualChunks: {
'vendor': ['react', 'react-dom'],
'ui': ['@mui/material'],
'utils': ['lodash', 'axios']
}
}
}
}
};
// 动态导入
button.addEventListener('click', async () => {
const {default: module} = await import('./heavy-module.js');
module.init();
});
HTTP缓存
// Service Worker缓存
self.addEventListener('fetch', (event) => {
event.respondWith(
caches.match(event.request).then((response) => {
return response || fetch(event.request);
})
);
});
// HTTP缓存头(服务端配置)
Cache-Control: public, max-age=31536000, immutable
🔍 性能监控
性能监控工具
// Web Vitals
import {getCLS, getFID, getLCP} from 'web-vitals';
getCLS(console.log);
getFID(console.log);
getLCP(console.log);
// 自定义监控
class PerformanceMonitor {
constructor() {
this.metrics = {};
}
mark(name) {
performance.mark(name);
}
measure(name, startMark, endMark) {
performance.measure(name, startMark, endMark);
const measure = performance.getEntriesByName(name)[0];
this.metrics[name] = measure.duration;
}
report() {
// 发送到分析服务
console.log('Performance Metrics:', this.metrics);
}
}
Lighthouse CI
# .github/workflows/lighthouse.yml
name: Lighthouse CI
on: [push]
jobs:
lighthouse:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Run Lighthouse CI
uses: treosh/lighthouse-ci-action@v9
with:
urls: |
https://example.com
uploadArtifacts: true
💡 性能优化检查清单
JavaScript
使用代码分割
懒加载非关键组件
避免不必要的重渲染
使用Web Workers处理密集计算
使用防抖/节流
CSS
使用CSS contain
避免复杂选择器
移除未使用的CSS
使用CSS动画而非JS动画
优化关键渲染路径
资源
压缩图片
使用WebP格式
图片懒加载
字体子集化
使用CDN
网络
启用HTTP/2
使用Gzip/Brotli压缩
设置缓存策略
减少HTTP请求
使用资源预加载
📚 工具推荐
分析工具
Lighthouse
WebPageTest
Chrome DevTools Performance
Bundle Analyzer
优化工具
ImageOptim(图片压缩)
SVGO(SVG优化)
PurgeCSS(移除未使用CSS)
Terser(JS压缩)