Appearance
前端性能优化面试题
1. 性能优化概述
问题:前端性能优化的目标是什么?有哪些关键指标?
答案:
性能优化目标:
- 提高页面加载速度
- 改善用户体验
- 提高搜索引擎排名
- 降低服务器负载
关键性能指标:
javascript
// 1. 首次内容绘制(FCP)
// 用户第一次看到内容的时间
// 目标:< 1.8s
// 2. 最大内容绘制(LCP)
// 页面主要内容完全渲染的时间
// 目标:< 2.5s
// 3. 首次输入延迟(FID)
// 用户首次交互的响应时间
// 目标:< 100ms
// 4. 累积布局偏移(CLS)
// 页面布局的稳定性
// 目标:< 0.1
// 5. 首字节时间(TTFB)
// 浏览器接收第一个字节的时间
// 目标:< 600ms
// 6. DOM 内容加载完成(DCL)
// DOM 解析完成的时间
// 7. 完全加载时间(L)
// 所有资源加载完成的时间性能监控:
javascript
// 使用 Performance API
window.addEventListener('load', () => {
const perfData = performance.getEntriesByType('navigation')[0];
console.log('DNS 查询:', perfData.domainLookupEnd - perfData.domainLookupStart);
console.log('TCP 连接:', perfData.connectEnd - perfData.connectStart);
console.log('请求响应:', perfData.responseEnd - perfData.requestStart);
console.log('DOM 解析:', perfData.domComplete - perfData.domInteractive);
console.log('页面加载:', perfData.loadEventEnd - perfData.fetchStart);
});
// 使用 PerformanceObserver
const observer = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
console.log(entry.name, entry.startTime, entry.duration);
}
});
observer.observe({ entryTypes: ['paint', 'largest-contentful-paint'] });2. 资源加载优化
问题:如何优化资源加载?
答案:
代码分割:
javascript
// Webpack 代码分割
// 1. 动态导入
import('./module.js').then(module => {
module.default();
});
// 2. 路由懒加载
const Home = () => import('./views/Home.vue');
const About = () => import('./views/About.vue');
const routes = [
{ path: '/', component: Home },
{ path: '/about', component: About }
];
// 3. 预加载
import(/* webpackPrefetch: true */ './module.js');
// 4. 预获取
import(/* webpackPreload: true */ './module.js');
// 5. SplitChunksPlugin 配置
module.exports = {
optimization: {
splitChunks: {
chunks: 'all',
minSize: 30000,
maxSize: 244000,
minChunks: 1,
maxAsyncRequests: 5,
maxInitialRequests: 3,
automaticNameDelimiter: '~',
cacheGroups: {
vendors: {
test: /[\\/]node_modules[\\/]/,
priority: -10
},
default: {
minChunks: 2,
priority: -20,
reuseExistingChunk: true
}
}
}
}
};资源压缩:
javascript
// 1. Gzip 压缩
// Webpack 配置
const CompressionPlugin = require('compression-webpack-plugin');
module.exports = {
plugins: [
new CompressionPlugin({
algorithm: 'gzip',
test: /\.(js|css|html|svg)$/,
threshold: 10240,
minRatio: 0.8
})
]
};
// 2. Brotli 压缩
const BrotliPlugin = require('brotli-webpack-plugin');
module.exports = {
plugins: [
new BrotliPlugin({
test: /\.(js|css|html|svg)$/,
threshold: 10240,
minRatio: 0.8
})
]
};
// 3. 图片压缩
const ImageMinimizerPlugin = require('image-minimizer-webpack-plugin');
module.exports = {
plugins: [
new ImageMinimizerPlugin({
minimizer: {
implementation: ImageMinimizerPlugin.imageminGenerate,
options: {
plugins: [
['gifsicle', { interlaced: true }],
['jpegtran', { progressive: true }],
['optipng', { optimizationLevel: 5 }],
['svgo', { plugins: [{ name: 'removeViewBox', active: false }] }]
]
}
}
})
]
};CDN 加速:
html
<!-- 使用 CDN 加载第三方库 -->
<script src="https://cdn.jsdelivr.net/npm/vue@3/dist/vue.global.js"></script>
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
<!-- 使用 CDN 加载图片 -->
<img src="https://cdn.example.com/images/photo.jpg" alt="Photo">
<!-- 使用 CDN 加载字体 -->
<link href="https://cdn.example.com/fonts/font.woff2" rel="preload" as="font" type="font/woff2" crossorigin>资源预加载:
html
<!-- 预加载关键资源 -->
<link rel="preload" href="/styles/main.css" as="style">
<link rel="preload" href="/scripts/main.js" as="script">
<link rel="preload" href="/fonts/main.woff2" as="font" type="font/woff2" crossorigin>
<!-- 预获取可能需要的资源 -->
<link rel="prefetch" href="/page2.html">
<link rel="prefetch" href="/images/photo.jpg">
<!-- DNS 预解析 -->
<link rel="dns-prefetch" href="//cdn.example.com">
<link rel="dns-prefetch" href="//api.example.com">
<!-- 预连接 -->
<link rel="preconnect" href="https://cdn.example.com">
<link rel="preconnect" href="https://api.example.com">3. 渲染性能优化
问题:如何优化渲染性能?
答案:
减少重排和重绘:
javascript
// 1. 批量 DOM 操作
// 不好:多次操作 DOM
for (let i = 0; i < 100; i++) {
document.body.innerHTML += '<div>Item ' + i + '</div>';
}
// 好:使用 DocumentFragment
const fragment = document.createDocumentFragment();
for (let i = 0; i < 100; i++) {
const div = document.createElement('div');
div.textContent = 'Item ' + i;
fragment.appendChild(div);
}
document.body.appendChild(fragment);
// 2. 使用虚拟 DOM
// React、Vue 等框架自动优化
const items = Array.from({ length: 100 }, (_, i) => ({ id: i, text: 'Item ' + i }));
// 3. 使用 CSS transform 代替 top/left
// 不好:触发重排
element.style.left = '100px';
element.style.top = '100px';
// 好:只触发合成
element.style.transform = 'translate(100px, 100px)';
// 4. 使用 opacity 代替 visibility
// 不好:触发重排
element.style.visibility = 'hidden';
// 好:只触发重绘
element.style.opacity = '0';
// 5. 批量读取和写入
// 不好:交错读写
const width1 = element.offsetWidth;
element.style.width = width1 + 'px';
const height1 = element.offsetHeight;
element.style.height = height1 + 'px';
// 好:批量读取后批量写入
const width = element.offsetWidth;
const height = element.offsetHeight;
element.style.width = width + 'px';
element.style.height = height + 'px';虚拟滚动:
javascript
// 虚拟列表实现
class VirtualList {
constructor(container, itemHeight, renderItem) {
this.container = container;
this.itemHeight = itemHeight;
this.renderItem = renderItem;
this.visibleItems = [];
this.startIndex = 0;
this.endIndex = 0;
this.init();
}
init() {
this.container.addEventListener('scroll', () => this.onScroll());
this.render();
}
onScroll() {
const scrollTop = this.container.scrollTop;
const containerHeight = this.container.clientHeight;
this.startIndex = Math.floor(scrollTop / this.itemHeight);
this.endIndex = Math.min(
this.startIndex + Math.ceil(containerHeight / this.itemHeight),
this.items.length - 1
);
this.render();
}
render() {
const fragment = document.createDocumentFragment();
for (let i = this.startIndex; i <= this.endIndex; i++) {
const item = this.renderItem(this.items[i], i);
item.style.position = 'absolute';
item.style.top = (i * this.itemHeight) + 'px';
fragment.appendChild(item);
}
this.container.innerHTML = '';
this.container.appendChild(fragment);
}
}
// 使用
const list = new VirtualList(
document.getElementById('list-container'),
50,
(item, index) => {
const div = document.createElement('div');
div.textContent = item.text;
return div;
}
);防抖和节流:
javascript
// 防抖
function debounce(func, delay) {
let timer;
return function(...args) {
clearTimeout(timer);
timer = setTimeout(() => func.apply(this, args), delay);
};
}
// 使用
window.addEventListener('resize', debounce(() => {
console.log('Resize event');
}, 300));
// 节流
function throttle(func, delay) {
let lastCall = 0;
return function(...args) {
const now = Date.now();
if (now - lastCall >= delay) {
lastCall = now;
func.apply(this, args);
}
};
}
// 使用
window.addEventListener('scroll', throttle(() => {
console.log('Scroll event');
}, 100));
// requestAnimationFrame 节流
function rafThrottle(func) {
let ticking = false;
return function(...args) {
if (!ticking) {
ticking = true;
requestAnimationFrame(() => {
func.apply(this, args);
ticking = false;
});
}
};
}
// 使用
window.addEventListener('scroll', rafThrottle(() => {
console.log('Scroll event');
}));4. 内存优化
问题:如何优化内存使用?
答案:
避免内存泄漏:
javascript
// 1. 及时清理事件监听器
// 不好:忘记移除监听器
function init() {
window.addEventListener('resize', handleResize);
}
// 好:在组件销毁时移除
function init() {
const handleResize = () => {
// 处理调整大小
};
window.addEventListener('resize', handleResize);
return () => {
window.removeEventListener('resize', handleResize);
};
}
const cleanup = init();
// 不需要时调用 cleanup();
// 2. 避免闭包中的大对象
// 不好:闭包引用大对象
function createHandler(largeData) {
return function() {
console.log(largeData.length);
};
}
// 好:只引用需要的数据
function createHandler(dataLength) {
return function() {
console.log(dataLength);
};
}
// 3. 及时清理定时器
// 不好:忘记清理定时器
function startTimer() {
setInterval(() => {
console.log('Tick');
}, 1000);
}
// 好:保存定时器 ID,可以清理
function startTimer() {
const timerId = setInterval(() => {
console.log('Tick');
}, 1000);
return () => clearInterval(timerId);
}
const stopTimer = startTimer();
stopTimer();
// 4. 使用 WeakMap 和 WeakSet
// WeakMap 不会阻止垃圾回收
const cache = new WeakMap();
function processData(obj) {
if (cache.has(obj)) {
return cache.get(obj);
}
const result = expensiveOperation(obj);
cache.set(obj, result);
return result;
}对象池:
javascript
// 对象池实现
class ObjectPool {
constructor(createFn, resetFn, initialSize = 10) {
this.createFn = createFn;
this.resetFn = resetFn;
this.pool = [];
for (let i = 0; i < initialSize; i++) {
this.pool.push(createFn());
}
}
acquire() {
return this.pool.pop() || this.createFn();
}
release(obj) {
this.resetFn(obj);
this.pool.push(obj);
}
}
// 使用
const pool = new ObjectPool(
() => ({ x: 0, y: 0 }),
(obj) => { obj.x = 0; obj.y = 0; }
);
function processItems(items) {
const results = [];
for (const item of items) {
const obj = pool.acquire();
obj.x = item.x;
obj.y = item.y;
const result = process(obj);
results.push(result);
pool.release(obj);
}
return results;
}5. 网络优化
问题:如何优化网络请求?
答案:
HTTP 缓存:
javascript
// 1. 设置缓存头
// 服务端
app.use((req, res, next) => {
res.setHeader('Cache-Control', 'public, max-age=3600');
res.setHeader('ETag', '"abc123"');
next();
});
// 2. 使用 Service Worker 缓存
// sw.js
self.addEventListener('install', (event) => {
event.waitUntil(
caches.open('v1').then((cache) => {
return cache.addAll([
'/',
'/styles/main.css',
'/scripts/main.js'
]);
})
);
});
self.addEventListener('fetch', (event) => {
event.respondWith(
caches.match(event.request).then((response) => {
return response || fetch(event.request);
})
);
});
// 3. 使用 IndexedDB 缓存
const request = indexedDB.open('cache', 1);
request.onupgradeneeded = (event) => {
const db = event.target.result;
const store = db.createObjectStore('data', { keyPath: 'id' });
store.createIndex('url', 'url', { unique: true });
};
request.onsuccess = (event) => {
const db = event.target.result;
// 缓存数据
const transaction = db.transaction(['data'], 'readwrite');
const store = transaction.objectStore('data');
store.put({ id: 1, url: '/api/data', data: {...} });
// 读取缓存
const getRequest = store.get(1);
getRequest.onsuccess = (e) => {
console.log(e.target.result);
};
};请求优化:
javascript
// 1. 合并请求
// 不好:多次请求
const requests = [
fetch('/api/user/1'),
fetch('/api/user/2'),
fetch('/api/user/3')
];
// 好:批量请求
const response = await fetch('/api/users?ids=1,2,3');
const users = await response.json();
// 2. 使用 GraphQL
const query = `
query {
user1: user(id: 1) { name email }
user2: user(id: 2) { name email }
user3: user(id: 3) { name email }
}
`;
const response = await fetch('/graphql', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ query })
});
// 3. 使用 HTTP/2 多路复用
// 自动支持,无需额外配置
// 4. 使用 WebSocket
const ws = new WebSocket('ws://api.example.com');
ws.onmessage = (event) => {
const data = JSON.parse(event.data);
console.log('Received:', data);
};
ws.send(JSON.stringify({ type: 'subscribe', channel: 'updates' }));数据压缩:
javascript
// 1. 请求压缩
const response = await fetch('/api/data', {
headers: {
'Accept-Encoding': 'gzip, deflate, br'
}
});
// 2. 响应压缩
// 服务端配置
app.use(compression({
filter: (req, res) => {
if (req.headers['x-no-compression']) {
return false;
}
return compression.filter(req, res);
},
threshold: 1024,
level: 6
}));
// 3. 数据格式优化
// 使用 Protocol Buffers 代替 JSON
// 使用 MessagePack 代替 JSON6. 图片优化
问题:如何优化图片加载?
答案:
图片格式选择:
html
<!-- 1. 使用 WebP 格式 -->
<picture>
<source srcset="image.webp" type="image/webp">
<source srcset="image.jpg" type="image/jpeg">
<img src="image.jpg" alt="Image">
</picture>
<!-- 2. 响应式图片 -->
<img
src="image-small.jpg"
srcset="image-small.jpg 500w, image-medium.jpg 1000w, image-large.jpg 1500w"
sizes="(max-width: 500px) 500px, (max-width: 1000px) 1000px, 1500px"
alt="Responsive image"
>
<!-- 3. 懒加载 -->
<img
src="placeholder.jpg"
data-src="image.jpg"
loading="lazy"
alt="Lazy loaded image"
>
<!-- 4. 预加载关键图片 -->
<link rel="preload" href="hero-image.jpg" as="image">图片压缩:
javascript
// 使用 sharp 压缩图片
const sharp = require('sharp');
async function compressImage(inputPath, outputPath) {
await sharp(inputPath)
.resize(800, 600, {
fit: 'inside',
withoutEnlargement: true
})
.jpeg({
quality: 80,
progressive: true
})
.toFile(outputPath);
}
// 使用 imagemin 压缩图片
const imagemin = require('imagemin');
const imageminJpegtran = require('imagemin-jpegtran');
const imageminPngquant = require('imagemin-pngquant');
await imagemin(['images/*.{jpg,png}'], {
destination: 'build/images',
plugins: [
imageminJpegtran({ progressive: true }),
imageminPngquant({ quality: [0.6, 0.8] })
]
});CSS Sprites:
css
/* 使用 CSS Sprites */
.icon {
background-image: url('sprite.png');
background-repeat: no-repeat;
}
.icon-home {
background-position: 0 0;
width: 32px;
height: 32px;
}
.icon-user {
background-position: -32px 0;
width: 32px;
height: 32px;
}
.icon-settings {
background-position: -64px 0;
width: 32px;
height: 32px;
}7. 字体优化
问题:如何优化字体加载?
答案:
字体格式选择:
html
<!-- 使用 WOFF2 格式 -->
@font-face {
font-family: 'CustomFont';
src: url('font.woff2') format('woff2');
font-weight: normal;
font-style: normal;
font-display: swap;
}
<!-- 字体回退 -->
@font-face {
font-family: 'CustomFont';
src: url('font.woff2') format('woff2'),
url('font.woff') format('woff'),
url('font.ttf') format('truetype');
font-weight: normal;
font-style: normal;
}字体加载策略:
css
/* font-display 属性 */
@font-face {
font-family: 'CustomFont';
src: url('font.woff2') format('woff2');
font-display: auto; /* 浏览器默认 */
font-display: block; /* 短暂隐藏文本 */
font-display: swap; /* 立即使用后备字体 */
font-display: fallback; /* 短暂使用后备字体 */
font-display: optional; /* 极短暂使用后备字体 */
}字体预加载:
html
<!-- 预加载字体 -->
<link rel="preload" href="font.woff2" as="font" type="font/woff2" crossorigin>
<!-- 预连接字体域名 -->
<link rel="preconnect" href="https://fonts.googleapis.com">字体子集化:
javascript
// 使用 fonttools 生成字体子集
const { Font } = require('fonteditor-core');
async function createFontSubset() {
const font = await Font.create({
type: 'ttf',
filename: 'font.ttf'
});
// 只保留需要的字符
const subset = font.subset({
text: 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'
});
// 导出子集字体
await subset.write({
type: 'woff2',
filename: 'font-subset.woff2'
});
}8. 构建优化
问题:如何优化构建过程?
答案:
Webpack 优化:
javascript
module.exports = {
// 1. 模式配置
mode: 'production',
// 2. 代码分割
optimization: {
splitChunks: {
chunks: 'all',
minSize: 30000,
maxSize: 244000
},
runtimeChunk: 'single'
},
// 3. Tree Shaking
optimization: {
usedExports: true,
sideEffects: false
},
// 4. 压缩
optimization: {
minimize: true,
minimizer: [
new TerserPlugin({
terserOptions: {
compress: {
drop_console: true,
drop_debugger: true
}
}
})
]
},
// 5. 缓存
cache: {
type: 'filesystem',
buildDependencies: {
config: [__filename]
}
},
// 6. 持久化缓存
output: {
filename: '[name].[contenthash:8].js',
chunkFilename: '[name].[contenthash:8].js'
},
plugins: [
// 7. 压缩 CSS
new MiniCssExtractPlugin({
filename: '[name].[contenthash:8].css'
}),
new CssMinimizerPlugin(),
// 8. 压缩图片
new ImageMinimizerPlugin({
minimizer: {
implementation: ImageMinimizerPlugin.imageminGenerate,
options: {
plugins: [
['gifsicle', { interlaced: true }],
['jpegtran', { progressive: true }],
['optipng', { optimizationLevel: 5 }]
]
}
}
})
]
};Vite 优化:
javascript
// vite.config.js
export default {
build: {
// 1. 代码分割
rollupOptions: {
output: {
manualChunks: {
'vendor': ['vue', 'vue-router', 'axios'],
'utils': ['lodash']
}
}
},
// 2. 压缩
minify: 'terser',
terserOptions: {
compress: {
drop_console: true,
drop_debugger: true
}
},
// 3. chunk 大小警告
chunkSizeWarningLimit: 1000,
// 4. CSS 代码分割
cssCodeSplit: true
},
// 5. 依赖预构建
optimizeDeps: {
include: ['vue', 'vue-router', 'axios']
},
plugins: [
// 6. 压缩图片
viteImagemin({
gifsicle: true,
optipng: true,
pngquant: true,
svgo: true,
jpegtran: true,
pluginsDefault: [
['gifsicle', { interlaced: true }],
['jpegtran', { progressive: true }],
['optipng', { optimizationLevel: 5 }]
]
})
]
};9. 性能监控和分析
问题:如何监控和分析性能?
答案:
性能监控:
javascript
// 1. Web Vitals 监控
import { getCLS, getFID, getLCP } from 'web-vitals';
getCLS(console.log);
getFID(console.log);
getLCP(console.log);
// 2. 自定义性能监控
class PerformanceMonitor {
constructor() {
this.metrics = {};
this.init();
}
init() {
// 监控页面加载
window.addEventListener('load', () => {
this.recordPageLoad();
});
// 监控资源加载
const observer = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
this.recordResource(entry);
}
});
observer.observe({ entryTypes: ['resource'] });
// 监控长任务
const longTaskObserver = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
this.recordLongTask(entry);
}
});
longTaskObserver.observe({ entryTypes: ['longtask'] });
}
recordPageLoad() {
const perfData = performance.getEntriesByType('navigation')[0];
this.metrics.pageLoad = {
dns: perfData.domainLookupEnd - perfData.domainLookupStart,
tcp: perfData.connectEnd - perfData.connectStart,
request: perfData.responseEnd - perfData.requestStart,
dom: perfData.domComplete - perfData.domInteractive,
load: perfData.loadEventEnd - perfData.fetchStart
};
}
recordResource(entry) {
if (!this.metrics.resources) {
this.metrics.resources = [];
}
this.metrics.resources.push({
name: entry.name,
duration: entry.duration,
size: entry.transferSize
});
}
recordLongTask(entry) {
if (!this.metrics.longTasks) {
this.metrics.longTasks = [];
}
this.metrics.longTasks.push({
duration: entry.duration,
startTime: entry.startTime
});
}
report() {
// 发送性能数据到服务器
fetch('/api/performance', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(this.metrics)
});
}
}
// 使用
const monitor = new PerformanceMonitor();
window.addEventListener('beforeunload', () => monitor.report());性能分析工具:
javascript
// 1. Chrome DevTools Performance
// 打开 DevTools -> Performance -> Record
// 2. Lighthouse
// npm install -g lighthouse
// lighthouse https://example.com --view
// 3. WebPageTest
// 访问 https://www.webpagetest.org/
// 4. PageSpeed Insights
// 访问 https://pagespeed.web.dev/
// 5. 使用 Performance API 分析
function analyzePerformance() {
const perfData = performance.getEntriesByType('navigation')[0];
console.log('DNS:', perfData.domainLookupEnd - perfData.domainLookupStart);
console.log('TCP:', perfData.connectEnd - perfData.connectStart);
console.log('TTFB:', perfData.responseStart - perfData.requestStart);
console.log('Download:', perfData.responseEnd - perfData.responseStart);
console.log('DOM:', perfData.domComplete - perfData.domInteractive);
console.log('Load:', perfData.loadEventEnd - perfData.fetchStart);
// 分析资源
const resources = performance.getEntriesByType('resource');
const slowResources = resources.filter(r => r.duration > 1000);
console.log('Slow resources:', slowResources);
}
analyzePerformance();10. 性能优化最佳实践
问题:前端性能优化有哪些最佳实践?
答案:
最佳实践清单:
javascript
// 1. 资源优化
// - 使用 CDN
// - 启用 Gzip/Brotli 压缩
// - 使用现代图片格式(WebP)
// - 图片懒加载
// - 字体子集化
// 2. 代码优化
// - 代码分割
// - Tree Shaking
// - 压缩代码
// - 移除未使用的代码
// - 使用现代 JavaScript 特性
// 3. 渲染优化
// - 减少重排和重绘
// - 使用虚拟 DOM
// - 虚拟滚动
// - 防抖和节流
// - 使用 CSS transform 和 opacity
// 4. 缓存优化
// - 使用 HTTP 缓存
// - 使用 Service Worker
// - 使用 IndexedDB
// - 使用 LocalStorage
// 5. 网络优化
// - 合并请求
// - 使用 WebSocket
// - 使用 HTTP/2
// - 数据压缩
// 6. 监控优化
// - 监控 Web Vitals
// - 监控错误
// - 监控性能指标
// - 使用 APM 工具
// 7. 构建优化
// - 使用生产模式
// - 代码分割
// - Tree Shaking
// - 压缩资源
// - 使用缓存
// 8. 用户体验优化
// - 骨架屏
// - 加载动画
// - 错误边界
// - 优雅降级
// 示例:骨架屏
function Skeleton() {
return (
<div className="skeleton">
<div className="skeleton-header" />
<div className="skeleton-body" />
<div className="skeleton-footer" />
</div>
);
}
// 示例:错误边界
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false };
}
static getDerivedStateFromError(error) {
return { hasError: true };
}
componentDidCatch(error, errorInfo) {
console.error('Error:', error, errorInfo);
}
render() {
if (this.state.hasError) {
return <div>Something went wrong.</div>;
}
return this.props.children;
}
}