Skip to content

前端性能优化面试指南

核心概念

前端性能优化是提升用户体验的关键环节,涉及加载性能、渲染性能、交互性能等多个维度。


性能指标

核心 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-revalidate

React/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:如何做性能瓶颈分析?

分析工具

  1. Chrome DevTools

    • Performance 面板:记录和分析性能
    • Memory 面板:内存分析和快照对比
    • Network 面板:网络请求分析
  2. Lighthouse

bash
# 命令行
npx lighthouse https://example.com --output html --output-path ./report.html

# Chrome DevTools
# Audits 面板 → Lighthouse
  1. WebPageTest
    • 全球多地点测试
    • 详细瀑布图分析
    • 视频帧对比

分析流程

javascript
// 1. 确定指标基线
const metrics = {
    LCP: 0,
    FID: 0,
    CLS: 0,
    TTFB: 0,
};

// 2. 分析瓶颈
// - Network:资源加载顺序、大小、请求数
// - Rendering:回流重绘、布局抖动
// - Scripting:长任务、JavaScript 执行时间

// 3. 优化并验证
// - 优化前后对比
// - 持续监控