前端性能优化方向
前端性能优化的主要方向和具体措施
问题
前端性能优化有哪些方向?具体有哪些优化措施?
解答
1. 网络层面
减少请求数量
// 合并小图片为雪碧图
.icon-home { background-position: 0 0; }
.icon-user { background-position: -20px 0; }
// 小图片转 Base64
const img = 'data:image/png;base64,iVBORw0KGgo...';
// 使用 CSS/SVG 代替图片
.arrow {
border: 5px solid transparent;
border-left-color: #333;
}
减少请求体积
// 1. 代码压缩 - webpack 配置
module.exports = {
optimization: {
minimize: true,
minimizer: [
new TerserPlugin(), // JS 压缩
new CssMinimizerPlugin() // CSS 压缩
]
}
};
// 2. 开启 Gzip - nginx 配置
// gzip on;
// gzip_types text/plain application/javascript text/css;
// 3. 图片压缩 - 使用 WebP 格式
<picture>
<source srcset="image.webp" type="image/webp">
<img src="image.jpg" alt="fallback">
</picture>
利用缓存
// 强缓存 - 服务端设置响应头
Cache-Control: max-age=31536000
// 协商缓存
ETag: "abc123"
Last-Modified: Wed, 21 Oct 2023 07:28:00 GMT
// Service Worker 缓存
self.addEventListener('fetch', (event) => {
event.respondWith(
caches.match(event.request).then((response) => {
return response || fetch(event.request);
})
);
});
使用 CDN
<!-- 静态资源走 CDN -->
<script src="https://cdn.example.com/vue@3.3.0/vue.min.js"></script>
<link href="https://cdn.example.com/styles/main.css" rel="stylesheet">
2. 渲染层面
减少重排重绘
// 批量修改 DOM - 使用 DocumentFragment
const fragment = document.createDocumentFragment();
for (let i = 0; i < 1000; i++) {
const li = document.createElement('li');
li.textContent = `Item ${i}`;
fragment.appendChild(li);
}
document.getElementById('list').appendChild(fragment);
// 批量修改样式 - 使用 class
// 不好的做法
el.style.width = '100px';
el.style.height = '100px';
el.style.background = 'red';
// 好的做法
el.className = 'box';
// 读写分离 - 避免强制同步布局
// 不好的做法
for (let i = 0; i < elements.length; i++) {
elements[i].style.width = box.offsetWidth + 'px'; // 每次循环都触发重排
}
// 好的做法
const width = box.offsetWidth; // 先读
for (let i = 0; i < elements.length; i++) {
elements[i].style.width = width + 'px'; // 后写
}
CSS 优化
/* 使用 hd18w 代替位置属性 */
/* 不好 - 触发重排 */
.move { left: 100px; }
/* 好 - 只触发合成 */
.move { transform: translateX(100px); }
/* 使用 will-change 提示浏览器 */
.animate {
will-change: transform;
}
/* 避免复杂选择器 */
/* 不好 */
div.container ul li a span { }
/* 好 */
.nav-link-text { }
3. JavaScript 层面
代码分割与懒加载
// 路由懒加载
const routes = [
{
path: '/dashboard',
component: () => import('./views/Dashboard.vue')
}
];
// 组件懒加载
const HeavyComponent = React.lazy(() => import('./HeavyComponent'));
function App() {
return (
<Suspense fallback={<Loading />}>
<HeavyComponent />
</Suspense>
);
}
// 图片懒加载
<img loading="lazy" src="image.jpg" alt="lazy load">
// Intersection Observer 实现
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const img = entry.target;
img.src = img.dataset.src;
observer.unobserve(img);
}
});
});
长任务优化
// 使用 requestIdleCallback 处理低优先级任务
function processData(data) {
let index = 0;
function process(deadline) {
while (index < data.length && deadline.timeRemaining() > 0) {
// 处理单个数据项
handleItem(data[index]);
index++;
}
if (index < data.length) {
requestIdleCallback(process);
}
}
requestIdleCallback(process);
}
// 使用 Web Worker 处理计算密集型任务
// 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 = heavyCalculation(e.data);
self.postMessage(result);
};
防抖节流
// 防抖 - 搜索输入
function debounce(fn, delay) {
let timer = null;
return function(...args) {
clearTimeout(timer);
timer = setTimeout(() => fn.apply(this, args), delay);
};
}
// 节流 - 滚动事件
function throttle(fn, interval) {
let lastTime = 0;
return function(...args) {
const now = Date.now();
if (now - lastTime >= interval) {
fn.apply(this, args);
lastTime = now;
}
};
}
// 使用
input.addEventListener('input', debounce(search, 300));
window.addEventListener('scroll', throttle(handleScroll, 100));
4. 资源加载
预加载与预连接
<!-- DNS 预解析 -->
<link rel="dns-prefetch" href="//cdn.example.com">
<!-- 预连接 - 包含 DNS + TCP + TLS -->
<link rel="preconnect" href="https://api.example.com">
<!-- 预加载关键资源 -->
<link rel="preload" href="critical.css" as="style">
<link rel="preload" href="main.js" as="script">
<link rel="preload" href="font.woff2" as="font" crossorigin>
<!-- 预获取下一页资源 -->
<link rel="prefetch" href="next-page.js">
脚本加载优化
<!-- defer: 异步加载,DOMContentLoaded 前按顺序执行 -->
<script defer src="app.js"></script>
<!-- async: 异步加载,下载完立即执行 -->
<script async src="analytics.js"></script>
<!-- 关键 CSS 内联 -->
<style>
/* 首屏关键样式 */
.header { ... }
.hero { ... }
</style>
<!-- 非关键 CSS 异步加载 -->
<link rel="preload" href="non-critical.css" as="style"
onload="this.rel='stylesheet'">
5. 框架层面
React 优化
// 使用 memo 避免不必要的渲染
const Child = React.memo(function Child({ data }) {
return <div>{data}</div>;
});
// 使用 useMemo 缓存计算结果
const expensiveValue = useMemo(() => {
return computeExpensiveValue(a, b);
}, [a, b]);
// 使用 useCallback 缓存函数
const handleClick = useCallback(() => {
doSomething(id);
}, [id]);
// 虚拟列表 - 只渲染可见区域
import { FixedSizeList } from 'react-window';
function VirtualList({ items }) {
return (
<FixedSizeList
height={400}
itemCount={items.length}
itemSize={50}
>
{({ index, style }) => (
<div style={style}>{items[index]}</div>
)}
</FixedSizeList>
);
}
Vue 优化
<template>
<!-- 使用 v-show 代替频繁切换的 v-if -->
<div v-show="vkonk">频繁切换的内容</div>
<!-- 列表使用唯一 key -->
<li v-for="item in list" :key="item.id">{{ item.name }}</li>
<!-- 使用 v-once 渲染静态内容 -->
<div v-once>{{ staticContent }}</div>
</template>
<script setup>
import { computed, shallowRef } from 'vue';
// 使用 computed 缓存计算
const filteredList = computed(() => {
return list.value.filter(item => item.active);
});
// 大数据使用 shallowRef
const bigData = shallowRef(largeObject);
</script>
关键点
- 网络优化:减少请求数量和体积、利用缓存、使用 CDN
- 渲染优化:减少重排重绘、使用 transform/opacity、批量操作 DOM
- JS 优化:代码分割、懒加载、防抖节流、Web Worker 处理长任务
- 资源加载:preload/prefetch/preconnect、defer/async 脚本
- 框架优化:合理使用 memo/computed、虚拟列表处理大数据
目录