其他优化方案
前端性能优化的其他实用方案
问题
除了常见的懒加载、代码分割外,还有哪些前端优化方案?
解答
1. 资源预加载
<!-- 预连接:提前建立连接 -->
<link rel="preconnect" href="https://api.example.com">
<!-- DNS 预解析 -->
<link rel="dns-prefetch" href="https://cdn.example.com">
<!-- 预加载:提前加载关键资源 -->
<link rel="preload" href="/fonts/main.woff2" as="font" crossorigin>
<link rel="preload" href="/critical.css" as="style">
<!-- 预获取:空闲时加载下一页资源 -->
<link rel="prefetch" href="/next-page.js">
2. Web Workers 处理耗时任务
// worker.js
self.onmessage = function(e) {
const { data } = e;
// 执行耗时计算
const result = heavyComputation(data);
self.postMessage(result);
};
function heavyComputation(data) {
// 模拟复杂计算
let sum = 0;
for (let i = 0; i < data.length; i++) {
sum += data[i] * Math.sqrt(data[i]);
}
return sum;
}
// main.js
const worker = new Worker('worker.js');
worker.postMessage(largeArray);
worker.onmessage = function(e) {
console.log('计算结果:', e.data);
};
3. 请求优化
// 请求合并
class RequestBatcher {
constructor(batchFn, delay = 50) {
this.queue = [];
this.batchFn = batchFn;
this.delay = delay;
this.timer = null;
}
add(id) {
return new Promise((resolve, reject) => {
this.queue.push({ id, resolve, reject });
if (!this.timer) {
this.timer = setTimeout(() => this.flush(), this.delay);
}
});
}
async flush() {
const batch = this.queue;
this.queue = [];
this.timer = null;
const ids = batch.map(item => item.id);
try {
// 批量请求
const results = await this.batchFn(ids);
batch.forEach((item, i) => item.resolve(results[i]));
} catch (err) {
batch.forEach(item => item.reject(err));
}
}
}
// 使用
const userBatcher = new RequestBatcher(async (ids) => {
const res = await fetch(`/api/users?ids=${ids.join(',')}`);
return res.json();
});
// 多次调用会合并成一次请求
userBatcher.add(1);
userBatcher.add(2);
userBatcher.add(3);
4. 虚拟列表
function VirtualList({ items, itemHeight, containerHeight }) {
const [scrollTop, setScrollTop] = useState(0);
// 计算可见范围
const startIndex = Math.floor(scrollTop / itemHeight);
const endIndex = Math.min(
startIndex + Math.ceil(containerHeight / itemHeight) + 1,
items.length
);
// 只渲染可见项
const visibleItems = items.slice(startIndex, endIndex);
const offsetY = startIndex * itemHeight;
return (
<div
style={{ height: containerHeight, overflow: 'auto' }}
onScroll={(e) => setScrollTop(e.target.scrollTop)}
>
<div style={{ height: items.length * itemHeight, position: 'relative' }}>
<div style={{ transform: `translateY(${offsetY}px)` }}>
{visibleItems.map((item, i) => (
<div key={startIndex + i} style={{ height: itemHeight }}>
{item.content}
</div>
))}
</div>
</div>
</div>
);
}
5. 骨架屏
/* 骨架屏动画 */
.skeleton {
background: linear-gradient(
90deg,
#f0f0f0 25%,
#e0e0e0 50%,
#f0f0f0 75%
);
background-size: 200% 100%;
animation: shimmer 1.5s infinite;
}
@keyframes shimmer {
0% { background-position: 200% 0; }
100% { background-position: -200% 0; }
}
function SkeletonCard() {
return (
<div className="card">
<div className="skeleton" style={{ width: 60, height: 60, borderRadius: '50%' }} />
<div className="skeleton" style={{ width: '80%', height: 20, marginTop: 12 }} />
<div className="skeleton" style={{ width: '60%', height: 16, marginTop: 8 }} />
</div>
);
}
6. 字体优化
/* 字体显示策略 */
@font-face {
font-family: 'CustomFont';
src: url('/fonts/custom.woff2') format('woff2');
font-display: swap; /* 先用系统字体,加载完再替换 */
}
/* 字体子集化:只加载需要的字符 */
@font-face {
font-family: 'CustomFont';
src: url('/fonts/custom-subset.woff2') format('woff2');
unicode-range: U+0000-00FF; /* 只包含基本拉丁字符 */
}
7. 避免布局抖动
// 错误:读写交替导致强制同步布局
elements.forEach(el => {
const width = el.offsetWidth; // 读
el.style.width = width + 10 + 'px'; // 写
});
// 正确:先批量读,再批量写
const widths = elements.map(el => el.offsetWidth); // 批量读
elements.forEach((el, i) => {
el.style.width = widths[i] + 10 + 'px'; // 批量写
});
// 使用 requestAnimationFrame 分离读写
function optimizedUpdate() {
// 读取阶段
const measurements = elements.map(el => el.getBoundingClientRect());
// 写入阶段
requestAnimationFrame(() => {
elements.forEach((el, i) => {
el.style.transform = `translateX(${measurements[i].width}px)`;
});
});
}
关键点
- preload/prefetch:关键资源预加载,空闲时预获取下一页
- Web Workers:将耗时计算移出主线程,避免阻塞 UI
- 请求合并:短时间内多次请求合并为一次,减少网络开销
- 虚拟列表:只渲染可视区域,解决长列表性能问题
- 避免布局抖动:批量读写 DOM,使用 hd18w 代替位置属性
目录