前端性能优化面试指南
核心概念
前端性能优化是提升用户体验的关键环节,涉及加载性能、渲染性能、交互性能等多个维度。
性能指标
核心 Web Vitals
| 指标 | 含义 | 达标标准 |
|---|---|---|
| LCP | 最大内容绘制 | < 2.5s |
| FID/INP | 首次输入延迟/交互延迟 | < 100ms |
| CLS | 累积布局偏移 | < 0.1 |
| FCP | 首次内容绘制 | < 1.8s |
| TTFB | 首字节到达时间 | < 800ms |
性能监测 API
javascript
// Performance Observer
const observer = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
console.log(entry.name, entry.startTime, entry.duration);
}
});
observer.observe({ entryTypes: ["paint", "longtask", "layout-shift"] });
// 获取关键指标
new PerformanceObserver((list) => {
const entries = list.getEntries();
const lcp = entries.find((e) => e.entryType === "largest-contentful-paint");
const cls = entries.find((e) => e.entryType === "layout-shift");
}).observe({ entryTypes: ["largest-contentful-paint", "layout-shift"] });加载性能优化
Q1:如何提升首屏加载性能?
答:
1. 资源加载优化
html
<!-- DNS 预解析 -->
<link rel="dns-prefetch" href="//cdn.example.com" />
<link rel="preconnect" href="https://cdn.example.com" crossorigin />
<!-- 预加载关键资源 -->
<link rel="preload" href="/fonts/main.woff2" as="font" crossorigin />
<link rel="preload" href="/js/bundle.js" as="script" />
<!-- 预渲染 -->
<link rel="prerender" href="https://example.com/about" />2. 代码分割
javascript
// Webpack
output: {
filename: '[name].[contenthash].js',
chunkFilename: '[name].[contenthash].chunk.js'
}
// Vite
build: {
rollupOptions: {
output: {
manualChunks: {
vendor: ['vue', 'vue-router']
}
}
}
}3. 路由懒加载
javascript
// React
const Home = React.lazy(() => import("./pages/Home"));
const About = React.lazy(() => import("./pages/About"));
// Vue
const routes = [{ path: "/home", component: () => import("./views/Home.vue") }];4. Tree Shaking
javascript
// 开启 Tree Shaking
// 1. 使用 ES Module
// 2. 配置 sideEffects
// package.json
{
"sideEffects": ["*.css"]
}
// webpack.config.js
optimization: {
usedExports: true
}5. 图片优化
html
<!-- 响应式图片 -->
<img
srcset="small.jpg 480w, large.jpg 1080w"
sizes="(max-width: 600px) 480px, 1080px"
src="large.jpg"
loading="lazy"
alt="description"
/>
<!-- 懒加载 -->
<img loading="lazy" src="image.jpg" alt="..." />
<!-- WebP 格式 -->
<picture>
<source srcset="image.webp" type="image/webp" />
<img src="image.jpg" alt="..." />
</picture>Q2:如何减少首屏加载时间?
答:
| 优化手段 | 实现方式 | 效果 |
|---|---|---|
| 代码压缩 | Terser/CSS Minimizer | 减少文件体积 |
| Gzip 压缩 | 服务器配置 | 体积减少 70% |
| Tree Shaking | 移除未使用代码 | 减少无用代码 |
| 代码分割 | 按需加载 | 减少初始加载量 |
| 预加载 | preload/prefetch | 提前加载资源 |
| 缓存策略 | 合理设置缓存 | 复用资源 |
服务器 Gzip 配置(Nginx):
nginx
gzip on;
gzip_vary on;
gzip_proxied any;
gzip_comp_level 6;
gzip_types text/plain text/css text/xml application/json application/javascript application/rss+xml application/atom+xml image/svg+xml;
gzip_min_length 1000;渲染性能优化
Q3:如何避免回流和重绘?
答:
回流(Reflow)触发条件:
- 元素尺寸、位置变化
- 添加/删除可见元素
- 内容变化(文本、图片)
- 浏览器窗口大小变化
重绘(Repaint)触发条件:
- 颜色、背景色变化
- 边框样式、阴影变化
- 不影响布局的变化
优化策略:
javascript
// 1. 批量 DOM 操作
const fragment = document.createDocumentFragment();
for (let i = 0; i < 100; i++) {
const li = document.createElement('li');
li.textContent = `Item ${i}`;
fragment.appendChild(li);
}
list.appendChild(fragment);
// 2. CSS 缓存
const el = document.getElementById('box');
el.style.transform = 'translateX(100px)'; // 不触发回流
el.style.opacity = '0.5'; // 不触发回流
// 3. 使用 CSS 属性替代 JavaScript 计算
// 避免
element.style.left = element.offsetLeft + 10 + 'px';
// 使用
element.style.transform = 'translateX(10px)'; // 使用 transform
// 4. 分离读写操作
// 错误:交替读写
element.style.width = element.offsetWidth + 'px'; // 读
element.style.height = element.offsetHeight + 'px'; // 读(可能触发回流)
// 正确:先读后写
const width = element.offsetWidth;
const height = element.offsetHeight;
element.style.width = width + 'px';
element.style.height = height + 'px';
// 5. 使用 will-change
.element {
will-change: transform; // 提前告知浏览器即将变化
transform: translateZ(0); // 或创建合成层
}Q4:CSS 渲染性能优化?
答:
选择器优化:
css
/* 避免:层级过深 */
.container .wrapper .content .box .item {
}
/* 推荐:保持选择器简洁 */
.box-item {
}
.container .box-item {
}
/* 避免:通配符 */
* {
box-sizing: border-box;
}
/* 避免:属性选择器 */
[class*="icon-"] {
}
/* 使用 BEM 命名 */
.block__element--modifier {
}渲染优化:
css
/* 使用 GPU 加速 */
.transform {
transform: translate3d(0, 0, 0);
}
.opacity {
opacity: 0.5;
}
/* 减少重绘元素 */
.immutable {
color: #333; /* 不易变化的属性 */
font-size: 16px;
}
.mutable {
transform: translateX(100px); /* 易变化的属性放一起 */
}
/* 避免布局抖动 */
@keyframes slide {
from {
transform: translateX(0);
}
to {
transform: translateX(100px);
}
}动画优化:
css
/* 优先使用 transform 和 opacity */
.animation {
animation: move 1s ease-out;
}
@keyframes move {
0% {
transform: translateX(0);
opacity: 0;
}
100% {
transform: translateX(100px);
opacity: 1;
}
}
/* 使用 will-change 提示 */
.animating-element {
will-change: transform, opacity;
}
/* 减少合成层 */
.layer {
transform: translateZ(0); /* 创建独立合成层 */
isolation: isolate; /* 隔离合成层 */
}JavaScript 性能优化
Q5:如何优化 JavaScript 执行性能?
答:
1. 防抖和节流
javascript
// 防抖:延迟执行,最后一次调用有效
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) {
lastTime = now;
fn.apply(this, args);
}
};
}
// 使用
window.addEventListener(
"resize",
debounce(() => {
console.log("resize");
}, 300),
);
window.addEventListener(
"scroll",
throttle(() => {
console.log("scroll");
}, 100),
);2. 事件委托
javascript
// 错误:为每个 item 添加事件
document.querySelectorAll(".item").forEach((item) => {
item.addEventListener("click", handleClick);
});
// 推荐:事件委托
list.addEventListener("click", (e) => {
const target = e.target.closest(".item");
if (target) {
handleClick(e, target);
}
});3. 虚拟列表
javascript
class VirtualList {
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();
}
render() {
const scrollTop = this.container.scrollTop;
this.startIndex = Math.floor(scrollTop / this.itemHeight);
const fragment = document.createDocumentFragment();
for (let i = 0; i < this.visibleCount + 2; i++) {
const index = this.startIndex + i;
if (index >= this.items.length) break;
const item = this.createItem(this.items[index], index);
fragment.appendChild(item);
}
this.container.innerHTML = "";
this.container.appendChild(fragment);
this.container.scrollTop = scrollTop;
}
}4. Web Worker
javascript
// worker.js
self.onmessage = function (e) {
const result = heavyComputation(e.data);
self.postMessage(result);
};
// main.js
const worker = new Worker("worker.js");
worker.postMessage(data);
worker.onmessage = function (e) {
console.log("Result:", e.data);
};Q6:内存泄漏及排查?
答:
常见内存泄漏场景:
javascript
// 1. 全局变量
function leak() {
leakData = "This is a leak"; // 未声明的全局变量
}
// 2. 定时器未清理
function init() {
setInterval(() => {
update();
}, 1000);
}
// 组件销毁时未 clearInterval
// 3. 事件监听未移除
element.addEventListener("click", handler);
// 组件销毁时未 removeEventListener
// 4. 闭包
function createLeak() {
const largeData = new Array(10000);
return function () {
console.log(largeData);
};
}
// 5. 循环引用
const obj1 = { ref: null };
const obj2 = { ref: null };
obj1.ref = obj2;
obj2.ref = obj1; // 循环引用排查方法:
javascript
// 1. Chrome DevTools Memory
// - Heap Snapshot:堆快照,对比分析
// - Allocation Timeline:分配时间线
// - Memory 面板监控
// 2. 代码排查
// 避免全局变量
"use strict";
// 清理定时器
const timer = setInterval(() => {}, 1000);
clearInterval(timer);
// 移除事件监听
const handler = () => {};
element.addEventListener("click", handler);
element.removeEventListener("click", handler);
// 使用 WeakMap/WeakSet
const cache = new WeakMap();
function process(obj) {
if (!cache.has(obj)) {
cache.set(obj, heavyComputation(obj));
}
return cache.get(obj);
}网络请求优化
Q7:如何优化网络请求?
答:
1. 请求合并
javascript
// 多个请求合并为一个
// 不好
fetch('/api/users');
fetch('/api/posts');
fetch('/api/comments');
// 好:使用 GraphQL 或 BFF
POST /api/batch
{
users: { ids: [1, 2, 3] },
posts: { userId: 1 }
}2. 缓存策略
javascript
// HTTP 缓存
// 强缓存
Cache-Control: public, max-age=31536000, immutable
// 协商缓存
Last-Modified: Mon, 23 Oct 2023 00:00:00 GMT
ETag: "abc123"
// Service Worker 缓存
self.addEventListener('fetch', (event) => {
event.respondWith(
caches.match(event.request).then((response) => {
return response || fetch(event.request);
})
);
});3. 并行请求
javascript
// Promise.all 并行
const [users, posts] = await Promise.all([
fetch("/api/users").then((r) => r.json()),
fetch("/api/posts").then((r) => r.json()),
]);
// 错误:串行
const users = await fetch("/api/users").then((r) => r.json());
const posts = await fetch("/api/posts").then((r) => r.json());4. 请求取消
javascript
// AbortController
const controller = new AbortController();
const signal = controller.signal;
fetch("/api/data", { signal })
.then((r) => r.json())
.catch((err) => {
if (err.name === "AbortError") {
console.log("Request cancelled");
}
});
// 取消请求
controller.abort();
// 组件中使用
useEffect(() => {
return () => controller.abort();
}, []);Q8:CDN 的作用和原理?
答:
CDN 工作原理:
用户请求
↓
DNS 解析 → 智能调度(最近节点)
↓
CDN 边缘节点
↓
缓存命中? → 是 → 返回缓存内容
↓ 否
回源服务器获取 → 缓存到边缘节点 → 返回给用户CDN 优化策略:
html
<!-- 静态资源使用 CDN -->
<script src="https://cdn.example.com/vue@3.2.0/vue.global.prod.js"></script>
<link rel="stylesheet" href="https://cdn.example.com/styles.css" />
<!-- 预连接 -->
<link rel="preconnect" href="https://cdn.example.com" />
<!-- 字体 -->
<link rel="preload" href="https://cdn.example.com/font.woff2" as="font" crossorigin />CDN 配置最佳实践:
javascript
// 缓存策略
// 1. 静态资源:长期缓存 + hash
Cache-Control: public, max-age=31536000, immutable
// 2. HTML:不缓存或短期缓存
Cache-Control: no-cache, no-store
// 3. API 响应:协商缓存
Cache-Control: private, max-age=0, must-revalidateReact/Vue 性能优化
Q9:React 性能优化手段?
答:
1. React.memo
javascript
// 函数组件缓存
const MyComponent = React.memo(function MyComponent({ name }) {
return <div>{name}</div>;
});
// 自定义比较函数
const MyComponent = React.memo(
function MyComponent({ name, age }) {
return (
<div>
{name} - {age}
</div>
);
},
(prevProps, nextProps) => {
return prevProps.name === nextProps.name;
},
);2. useMemo 和 useCallback
javascript
const MemoizedValue = useMemo(() => {
return computeExpensiveValue(a, b);
}, [a, b]);
const MemoizedCallback = useCallback(() => {
return doSomething(a, b);
}, [a, b]);3. 列表渲染优化
javascript
// 使用 key 但避免使用 index
{
todos.map((todo, index) => (
<Todo
key={todo.id} // 使用唯一 ID
todo={todo}
/>
));
}4. 懒加载
javascript
const LazyComponent = React.lazy(() => import("./HeavyComponent"));
function App() {
return (
<Suspense fallback={<Loading />}>
<LazyComponent />
</Suspense>
);
}Q10:Vue 性能优化手段?
答:
1. computed 缓存
javascript
// 使用 computed 替代 methods
computed: {
// 有缓存,基于响应式依赖
reversedMessage() {
return this.message.split('').reverse().join('');
}
}2. v-for 优化
vue
<!-- 使用唯一 key -->
<li v-for="item in items" :key="item.id">
<!-- 避免 v-if 和 v-for 一起用 -->
<li v-for="item in visibleItems" :key="item.id" v-if="item.show">
<!-- 应该先计算 visibleItems -->3. 组件懒加载
javascript
// 路由懒加载
const routes = [
{
path: "/about",
component: () => import("./views/About.vue"),
},
];
// 组件懒加载
export default {
components: {
HeavyComponent: () => import("./HeavyComponent.vue"),
},
};4. keep-alive
vue
<keep-alive include="Home,About" :max="10">
<router-view></router-view>
</keep-alive>面试综合问题
Q11:性能优化的完整思路?
答:
1. 性能监控与度量
javascript
// 使用 Performance API
const timing = performance.timing;
const loadTime = timing.loadEventEnd - timing.navigationStart;
// 使用 Web Vitals
import { getCLS, getFID, getLCP } from "web-vitals";
getCLS(console.log);
getFID(console.log);
getLCP(console.log);2. 优化优先级
┌────────────────────────────────────────────┐
│ 优化优先级 │
├────────────────────────────────────────┤
│ 1. 关键渲染路径优化(最快见效) │
│ 2. 资源加载优化(CDN、压缩、缓存) │
│ 3. JavaScript 执行优化(防抖、节流、节流)│
│ 4. 渲染优化(减少回流重绘) │
│ 5. React/Vue 特定优化 │
└────────────────────────────────────────────┘3. 性能优化清单
| 类别 | 优化项 | 目标 |
|---|---|---|
| 加载 | 代码分割、懒加载 | 减少首屏代码量 |
| 加载 | Gzip 压缩 | 减少传输体积 |
| 加载 | 浏览器缓存 | 提高复访速度 |
| 渲染 | CSS 优化 | 减少渲染阻塞 |
| 渲染 | 图片优化 | 减少资源体积 |
| 交互 | 防抖节流 | 减少无效计算 |
| 内存 | 内存管理 | 避免泄漏 |
Q12:如何做性能瓶颈分析?
答:
分析工具:
Chrome DevTools
- Performance 面板:记录和分析性能
- Memory 面板:内存分析和快照对比
- Network 面板:网络请求分析
Lighthouse
bash
# 命令行
npx lighthouse https://example.com --output html --output-path ./report.html
# Chrome DevTools
# Audits 面板 → Lighthouse- WebPageTest
- 全球多地点测试
- 详细瀑布图分析
- 视频帧对比
分析流程:
javascript
// 1. 确定指标基线
const metrics = {
LCP: 0,
FID: 0,
CLS: 0,
TTFB: 0,
};
// 2. 分析瓶颈
// - Network:资源加载顺序、大小、请求数
// - Rendering:回流重绘、布局抖动
// - Scripting:长任务、JavaScript 执行时间
// 3. 优化并验证
// - 优化前后对比
// - 持续监控