本文档系统梳理前端代码优化与性能优化的核心策略,涵盖加载优化、渲染优化、运行时优化、构建优化等多个维度。
目录
一、性能指标与测量工具
1.1 核心性能指标 (Core Web Vitals)
| 指标 | 全称 | 目标值 | 说明 |
|---|---|---|---|
| LCP | Largest Contentful Paint | ≤2.5s | 最大内容绘制时间,衡量首屏加载速度 |
| FID | First Input Delay | ≤100ms | 首次输入延迟,衡量交互响应速度 |
| CLS | Cumulative Layout Shift | ≤0.1 | 累积布局偏移,衡量视觉稳定性 |
| FCP | First Contentful Paint | ≤1.8s | 首次内容绘制时间 |
| TTI | Time to Interactive | ≤3.8s | 可交互时间 |
| TBT | Total Blocking Time | ≤200ms | 总阻塞时间 |
1.2 常用测量工具
┌─────────────────────────────────────────────────────────────┐
│ Lighthouse │ Chrome 内置,全面评分 + 优化建议 │
├─────────────────────────────────────────────────────────────┤
│ Chrome DevTools │ Performance 面板深度分析 │
├─────────────────────────────────────────────────────────────┤
│ WebPageTest │ 多地区、多网络环境测试 │
├─────────────────────────────────────────────────────────────┤
│ Web Vitals Ext │ 实时查看当前页面指标 │
├─────────────────────────────────────────────────────────────┤
│ Sentry/ARMS │ 线上真实用户监控 (RUM) │
└─────────────────────────────────────────────────────────────┘
二、加载性能优化
2.1 资源压缩与优化
代码压缩
- JS 压缩: 使用 Terser 移除空格、注释,缩短变量名
- CSS 压缩: 使用 CssMinimizerPlugin 优化
- HTML 压缩: 移除空白字符和注释
// Vite 配置示例
export default defineConfig({
build: {
minify: 'terser',
cssCodeSplit: true,
rollupOptions: {
output: {
manualChunks: {
vendor: ['vue', 'vue-router'],
ui: ['element-plus']
}
}
}
}
})
Tree Shaking 树优化
// ✅ 使用 ES Module,支持 Tree Shaking
import { debounce } from 'lodash-es'
// ❌ 避免使用 CommonJS,无法 Tree Shaking
const _ = require('lodash')
图片优化
| 格式 | 压缩率 | 适用场景 |
|---|---|---|
| AVIF | 比 JPEG 小 50%+ | 现代浏览器首选 |
| WebP | 比 JPEG 小 25-35% | 兼容性较好的选择 |
| JPEG | 基础格式 | 通用兼容 |
| PNG | 无损 | 需要透明通道 |
<!-- 响应式图片 -->
<picture>
<source srcset="image.avif" type="image/avif">
<source srcset="image.webp" type="image/webp">
<img src="image.jpg" alt="示例" loading="lazy">
</picture>
2.2 代码分割与懒加载
路由懒加载
// Vue 路由懒加载
const routes = [
{
path: '/home',
component: () => import(/* webpackChunkName: "home" */ './views/Home.vue')
},
{
path: '/about',
component: () => import(/* webpackChunkName: "about" */ './views/About.vue')
}
]
// React 路由懒加载
import { lazy, Suspense } from 'react'
const Home = lazy(() => import('./views/Home'))
function App() {
return (
<Suspense fallback={<div>加载中...</div>}>
<Home />
</Suspense>
)
}
组件懒加载
// 动态导入组件
const HeavyComponent = defineAsyncComponent(() =>
import('./components/HeavyComponent.vue')
)
图片懒加载
<!-- 原生懒加载 -->
<img src="image.jpg" loading="lazy" alt="示例">
<!-- Intersection Observer 实现 -->
<script>
const imageObserver = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const img = entry.target
img.src = img.dataset.src
img.classList.remove('lazy')
imageObserver.unobserve(img)
}
})
})
document.querySelectorAll('img.lazy').forEach(img => {
imageObserver.observe(img)
})
</script>
2.3 预加载策略
<!-- 预连接 - 提前建立 TCP 连接 -->
<link rel="preconnect" href="https://cdn.example.com" crossorigin>
<!-- DNS 预解析 -->
<link rel="dns-prefetch" href="https://api.example.com">
<!-- 预加载关键资源 -->
<link rel="preload" href="/fonts/main.woff2" as="font" type="font/woff2" crossorigin>
<link rel="preload" href="/css/critical.css" as="style">
<!-- 预获取下一页资源 -->
<link rel="prefetch" href="/next-page.js">
<!-- 预渲染整个页面 -->
<link rel="prerender" href="/next-page.html">
2.4 减少渲染阻塞
<!-- CSS 异步加载 -->
<link rel="stylesheet" href="non-critical.css" media="print" onload="this.media='all'">
<!-- JS 异步加载 -->
<script src="app.js" defer></script> <!-- 延迟执行,保持顺序 -->
<script src="analytics.js" async></script> <!-- 异步加载,无序执行 -->
<!-- 关键 CSS 内联 -->
<style>
/* 首屏关键样式 */
.hero { ... }
.header { ... }
</style>
2.5 CDN 加速
// 静态资源 CDN 配置
export default defineConfig({
base: 'https://cdn.example.com/',
build: {
assetsDir: 'static',
rollupOptions: {
output: {
entryFileNames: 'js/[name]-[hash].js',
chunkFileNames: 'js/[name]-[hash].js',
assetFileNames: (assetInfo) => {
const info = assetInfo.name.split('.')
const ext = info[info.length - 1]
return `assets/[name]-[hash][extname]`
}
}
}
}
})
三、代码优化技巧
3.1 JavaScript 代码优化
减少 DOM 操作
// ❌ 低效:多次操作 DOM
for (let i = 0; i < 100; i++) {
document.getElementById('list').innerHTML += `<li>${i}</li>`
}
// ✅ 高效:批量操作,使用 DocumentFragment
const fragment = document.createDocumentFragment()
for (let i = 0; i < 100; i++) {
const li = document.createElement('li')
li.textContent = i
fragment.appendChild(li)
}
document.getElementById('list').appendChild(fragment)
// ✅ 或使用 innerHTML 一次性插入
let html = ''
for (let i = 0; i < 100; i++) {
html += `<li>${i}</li>`
}
document.getElementById('list').innerHTML = html
事件委托
// ❌ 低效:每个子元素绑定事件
document.querySelectorAll('.item').forEach(item => {
item.addEventListener('click', handleClick)
})
// ✅ 高效:事件委托到父元素
document.getElementById('list').addEventListener('click', (e) => {
if (e.target.classList.contains('item')) {
handleClick(e)
}
})
防抖与节流
// 防抖 - 延迟执行,适用于搜索输入
function debounce(fn, delay) {
let timer = null
return function (...args) {
clearTimeout(timer)
timer = setTimeout(() => fn.apply(this, args), delay)
}
}
// 节流 - 控制执行频率,适用于滚动、resize
function throttle(fn, limit) {
let inThrottle = false
return function (...args) {
if (!inThrottle) {
fn.apply(this, args)
inThrottle = true
setTimeout(() => inThrottle = false, limit)
}
}
}
// 使用示例
window.addEventListener('scroll', throttle(handleScroll, 100))
searchInput.addEventListener('input', debounce(handleSearch, 300))
数据结构优化
// ✅ 使用 Map 替代 Object 进行高频查找
const map = new Map()
map.set('key', value)
const value = map.get('key') // O(1)
// ✅ 使用 Set 进行数组去重
const unique = [...new Set(array)]
// ✅ 缓存计算结果
const memoize = (fn) => {
const cache = new Map()
return (...args) => {
const key = JSON.stringify(args)
if (cache.has(key)) return cache.get(key)
const result = fn(...args)
cache.set(key, result)
return result
}
}
循环优化
// ✅ 缓存数组长度
for (let i = 0, len = array.length; i < len; i++) {
// ...
}
// ✅ 倒序循环(更快)
for (let i = array.length; i--;) {
// ...
}
// ✅ 使用 for 循环替代 forEach(性能更好)
for (let i = 0; i < array.length; i++) {
process(array[i])
}
3.2 CSS 代码优化
选择器优化
/* ❌ 低效:深层嵌套选择器 */
div.container ul li a span { }
/* ✅ 高效:使用单一类名 */
.nav-link { }
/* ✅ 避免通配符选择器 */
/* ❌ * { margin: 0; } */
/* ✅ 具体选择器 */
body, h1, h2, p { margin: 0; }
避免触发重排
/* ❌ 触发重排(Reflow) */
.element {
width: 100px;
height: 100px;
margin: 10px;
padding: 20px;
}
/* ✅ 使用 transform 和 opacity(仅触发重绘或合成) */
.element {
transform: translateX(100px) scale(1.2);
opacity: 0.8;
will-change: transform; /* 提示浏览器优化 */
}
CSS 动画优化
/* ✅ 使用 CSS 动画替代 JS 动画 */
@keyframes slideIn {
from { transform: translateX(-100%); }
to { transform: translateX(0); }
}
.animated {
animation: slideIn 0.3s ease-out;
/* 确保动画元素脱离文档流 */
position: absolute;
/* 使用 GPU 加速 */
transform: translateZ(0);
}
字体加载优化
@font-face {
font-family: 'CustomFont';
src: url('font.woff2') format('woff2');
font-display: swap; /* 先显示后备字体,加载后替换 */
font-weight: 400;
font-style: normal;
}
body {
font-family: 'CustomFont', system-ui, sans-serif;
}
3.3 HTML 优化
<!-- ✅ 压缩 HTML -->
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<!-- 视口设置 -->
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<!-- 预连接 -->
<link rel="preconnect" href="https://cdn.example.com">
<!-- 关键 CSS 内联 -->
<style>/* 关键样式 */</style>
<!-- 异步加载非关键 CSS -->
<link rel="preload" href="styles.css" as="style" onload="this.onload=null;this.rel='stylesheet'">
</head>
<body>
<!-- 内容
<!-- 延迟加载 JS -->
<script src="app.js" defer></script>
</body>
</html>
四、渲染性能优化
4.1 减少重排与重绘
// ❌ 多次读取/写入导致多次重排
const height = element.offsetHeight // 读取
element.style.height = (height + 10) + 'px' // 写入
const width = element.offsetWidth // 读取(强制同步布局)
element.style.width = (width + 10) + 'px' // 写入
// ✅ 批量读取,批量写入
const height = element.offsetHeight
const width = element.offsetWidth
requestAnimationFrame(() => {
element.style.height = (height + 10) + 'px'
element.style.width = (width + 10) + 'px'
})
// ✅ 使用 CSS 类批量修改
const classNames = ['expanded', 'active', 'visible']
element.classList.add(...classNames)
4.2 虚拟列表
// 虚拟列表核心思想:只渲染视口内的元素
function VirtualList({ items, itemHeight, height }) {
const [scrollTop, setScrollTop] = useState(0)
const visibleCount = Math.ceil(height / itemHeight)
const startIndex = Math.floor(scrollTop / itemHeight)
const endIndex = Math.min(startIndex + visibleCount, items.length)
const visibleItems = items.slice(startIndex, endIndex)
const offsetY = startIndex * itemHeight
return (
<div
style={{ height, overflow: 'auto' }}
onScroll={(e) => setScrollTop(e.target.scrollTop)}
>
<div style={{ height: items.length * itemHeight }}>
<div style={{ transform: `translateY(${offsetY}px)` }}>
{visibleItems.map(item => (
<div key={item.id} style={{ height: itemHeight }}>
{item.content}
</div>
))}
</div>
</div>
</div>
)
}
4.3 虚拟 DOM 优化
// Vue 优化
// ✅ v-for 使用 key
<template>
<div v-for="item in list" :key="item.id">{{ item.name }}</div>
</template>
// ✅ v-show 替代频繁切换的 v-if
<template>
<div v-show="isVisible">内容</div>
</template>
// ✅ computed 缓存计算属性
<script>
export default {
computed: {
filteredList() {
return this.list.filter(item => item.active)
}
}
}
</script>
// React 优化
// ✅ React.memo 缓存组件
const ListItem = React.memo(({ item }) => {
return <div>{item.name}</div>
})
// ✅ useMemo 缓存计算结果
const filteredList = useMemo(() => {
return list.filter(item => item.active)
}, [list])
// ✅ useCallback 缓存函数
const handleClick = useCallback((id) => {
console.log(id)
}, [])
4.4 分层与合成
/* 使用 will-change 提示浏览器优化 */
.animated-element {
will-change: transform, opacity;
}
/* 动画结束后移除 will-change */
.animated-element.animation-complete {
will-change: auto;
}
/* 创建新的合成层 */
.gpu-layer {
transform: translateZ(0);
backface-visibility: hidden;
}
五、运行时性能优化
5.1 内存管理
// ✅ 清除事件监听器
useEffect(() => {
const handleScroll = () => { /* ... */ }
window.addEventListener('scroll', handleScroll)
return () => {
window.removeEventListener('scroll', handleScroll)
}
}, [])
// ✅ 清除定时器
useEffect(() => {
const timer = setInterval(() => { /* ... */ }, 1000)
return () => {
clearInterval(timer)
}
}, [])
// ✅ 使用 WeakMap 避免内存泄漏
const cache = new WeakMap()
function process(obj) {
if (!cache.has(obj)) {
cache.set(obj, heavyComputation(obj))
}
return cache.get(obj)
}
// ✅ 手动释放大对象
function processLargeData() {
let largeData = fetchLargeData()
// 处理数据...
const result = process(largeData)
largeData = null // 释放引用
return result
}
5.2 Web Worker
// worker.js
self.onmessage = function(e) {
const data = e.data
// 执行耗时计算
const result = heavyComputation(data)
self.postMessage(result)
}
// main.js
const worker = new Worker('worker.js')
worker.postMessage(largeData)
worker.onmessage = function(e) {
const result = e.data
// 处理结果
}
// 使用 Comlink 简化通信
import * as Comlink from 'comlink'
const WorkerClass = Comlink.wrap(new Worker('./worker.js'))
const instance = await new WorkerClass()
const result = await instance.process(data)
5.3 任务分解
// ✅ 使用 requestIdleCallback 执行低优先级任务
requestIdleCallback((deadline) => {
while (deadline.timeRemaining() > 0 && tasks.length > 0) {
const task = tasks.shift()
task()
}
if (tasks.length > 0) {
requestIdleCallback(processTasks)
}
})
// ✅ 使用 requestAnimationFrame 分解渲染任务
function processChunk(items, processItem, chunkSize = 10) {
let index = 0
function process() {
const end = Math.min(index + chunkSize, items.length)
for (let i = index; i < end; i++) {
processItem(items[i])
}
index = end
if (index < items.length) {
requestAnimationFrame(process)
}
}
requestAnimationFrame(process)
}
5.4 长任务优化
// ✅ 使用 Scheduler 库调度任务
import { unstable_scheduleCallback } from 'scheduler'
unstable_scheduleCallback(() => {
// 低优先级任务
})
// ✅ React 18 并发特性
import { startTransition } from 'react'
function handleChange(value) {
// 紧急更新
setInputValue(value)
// 非紧急更新
startTransition(() => {
setSearchResults(search(value))
})
}
六、构建与部署优化
6.1 构建优化
// Vite 优化配置
export default defineConfig({
build: {
// 代码分割
rollupOptions: {
output: {
manualChunks: {
// 第三方库打包到一起
vendor: ['vue', 'vue-router', 'pinia'],
// UI 组件库单独打包
ui: ['element-plus'],
// 工具库
utils: ['lodash-es', 'dayjs']
},
// 资源文件名添加 hash
entryFileNames: 'js/[name]-[hash].js',
chunkFileNames: 'js/[name]-[hash].js',
assetFileNames: (assetInfo) => {
const info = assetInfo.name.split('.')
const ext = info[info.length - 1]
if (/\.(png|jpe?g|gif|svg|webp|avif)$/.test(assetInfo.name)) {
return 'images/[name]-[hash][extname]'
}
if (/\.css$/.test(assetInfo.name)) {
return 'css/[name]-[hash][extname]'
}
return 'assets/[name]-[hash][extname]'
}
}
},
// 压缩
minify: 'terser',
terserOptions: {
compress: {
drop_console: true,
drop_debugger: true
}
},
// 资源内联阈值
assetsInlineLimit: 4096,
// 开启 CSS 代码分割
cssCodeSplit: true,
// 预渲染
ssr: false
},
// 优化依赖预构建
optimizeDeps: {
include: ['vue', 'vue-router', 'pinia', 'element-plus']
}
})
6.2 Gzip/Brotli 压缩
# Nginx 配置
server {
gzip on;
gzip_vary on;
gzip_min_length 1024;
gzip_types text/plain text/css application/json application/javascript text/xml;
# Brotli 压缩(效果更好)
brotli on;
brotli_comp_level 6;
brotli_types text/plain text/css application/json application/javascript;
}
6.3 HTTP/2 与 HTTP/3
# HTTP/2 配置
server {
listen 443 ssl http2;
# ...
}
# HTTP/3 配置
server {
listen 443 quic reuseport;
listen 443 ssl;
ssl_protocols TLSv1.3;
add_header Alt-Svc 'h3=":443"; ma=86400';
# ...
}
6.4 SSR 与 SSG
// Next.js SSR
export async function getServerSideProps(context) {
const data = await fetchData()
return {
props: { data }
}
}
// Next.js SSG
export async function getStaticProps() {
const data = await fetchData()
return {
props: { data },
revalidate: 60 // ISR 增量静态再生
}
}
// Nuxt.js SSR
<script setup>
const { data } = await useFetch('/api/data')
</script>
七、缓存策略
7.1 HTTP 缓存
# 强缓存 - 长期缓存静态资源
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2)$ {
expires 1y;
add_header Cache-Control "public, immutable";
}
# 协商缓存 - HTML 文件
location ~* \.html$ {
add_header Cache-Control "no-cache";
}
# API 响应缓存
location /api/ {
add_header Cache-Control "private, max-age=300";
}
7.2 Service Worker
// service-worker.js
const CACHE_NAME = 'app-v1'
const urlsToCache = [
'/',
'/styles.css',
'/app.js',
'/icon.png'
]
// 安装时缓存核心资源
self.addEventListener('install', (event) => {
event.waitUntil(
caches.open(CACHE_NAME)
.then(cache => cache.addAll(urlsToCache))
)
})
// 拦截请求,优先从缓存读取
self.addEventListener('fetch', (event) => {
event.respondWith(
caches.match(event.request)
.then(response => {
// 缓存命中直接返回
if (response) {
return response
}
// 否则发起网络请求
return fetch(event.request)
.then(response => {
// 缓存新资源
if (response.status === 200) {
const responseClone = response.clone()
caches.open(CACHE_NAME)
.then(cache => cache.put(event.request, responseClone))
}
return response
})
})
)
})
八、监控与持续优化
8.1 性能监控
// Web Vitals 监控
import { onCLS, onFID, onLCP } from 'web-vitals'
onCLS((metric) => {
console.log('CLS:', metric)
// 上报到监控系统
sendToAnalytics('CLS', metric)
})
onFID((metric) => {
console.log('FID:', metric)
sendToAnalytics('FID', metric)
})
onLCP((metric) => {
console.log('LCP:', metric)
sendToAnalytics('LCP', metric)
})
8.2 错误监控
// 全局错误捕获
window.addEventListener('error', (event) => {
console.error('Global error:', event.error)
sendToSentry(event.error)
})
// Promise 错误捕获
window.addEventListener('unhandledrejection', (event) => {
console.error('Unhandled rejection:', event.reason)
sendToSentry(event.reason)
})
8.3 性能优化流程
┌─────────────────────────────────────────────────────────────┐
│ 1. 基准测试 │ 使用 Lighthouse 获取基准分数 │
├─────────────────────────────────────────────────────────────┤
│ 2. 识别瓶颈 │ 分析 Performance 面板,找到性能瓶颈 │
├─────────────────────────────────────────────────────────────┤
│ 3. 应用优化 │ 根据瓶颈类型应用对应优化策略 │
├─────────────────────────────────────────────────────────────┤
│ 4. 验证效果 │ 重新测试,对比优化前后的指标变化 │
├─────────────────────────────────────────────────────────────┤
│ 5. 持续监控 │ 部署后持续监控,及时发现问题 │
└─────────────────────────────────────────────────────────────┘
结语
前端性能优化是一个持续的过程,需要结合项目实际情况选择合适的优化策略。建议:
- 先测量后优化:使用性能工具获取基准数据,避免盲目优化
- 优先级排序:优先解决影响用户体验最大的性能问题
- 渐进式优化:从小处着手,逐步优化,每次优化后验证效果
- 持续监控:建立性能监控体系,及时发现性能退化