面试中,面试官经常问关于前端性能优化的问题,前端性能优化已成为打造卓越用户体验的关键要素。一个响应迅速、加载流畅的网页,能够极大地提升用户满意度,增加用户留存率。
渲染层面的性能优化
重绘与重排:理解渲染的底层逻辑
重绘和重排是渲染过程中的两个关键概念。重绘指的是元素外观的改变,如背景颜色、文字颜色的调整,但不涉及布局的变动。由于不影响布局,重绘的代价相对较低。而重排则是指元素布局的改变,包括位置、大小的调整,以及元素的隐藏或显示等。一旦发生重排,浏览器需要重新计算布局,这可能会影响到其他元素的位置和大小。需要注意的是,重排必定会引发重绘,而重绘不一定会导致重排。
以下代码示例展示了如何通过批量修改 DOM 来减少重排和重绘的次数:
xml
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
</head>
<body>
<div id="myDiv">Hello, World!</div>
<script>
const div = document.getElementById('myDiv');
// 欠佳做法:多次修改 DOM 会触发多次重排
// div.style.color = 'red';
// div.style.fontSize = '20px';
// div.style.padding = '10px';
// 优化做法:批量修改 DOM
div.style.cssText = 'color: red; font-size: 20px; padding: 10px;';
</script>
</body>
</html>
文档碎片:高效构建 DOM 的秘密武器
文档碎片是一种轻量级的 DOM 容器,它允许我们在内存中构建 DOM 结构,最后一次性将其插入到文档中。这样做可以显著减少重排和重绘的次数,提高渲染性能。
xml
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
</head>
<body>
<ul id="myList"></ul>
<script>
const list = document.getElementById('myList');
const fragment = document.createDocumentFragment();
for (let i = 0; i < 10; i++) {
const li = document.createElement('li');
li.textContent = `Item ${i}`;
fragment.appendChild(li);
}
list.appendChild(fragment);
</script>
</body>
</html>
资源加载优化
图片懒加载:节省首屏加载资源的良方
图片懒加载是一种有效的资源加载优化策略,它可以避免在首屏加载时一次性加载所有图片,从而减少资源消耗,提高页面加载速度。我们可以使用 getBoundingClientRect()
方法或 IntersectionObserver
API 来实现图片懒加载。
xml
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
</head>
<body>
<img class="lazy" data-src="https://picsum.photos/200/300" alt="Lazy Image">
<script>
const lazyImages = document.querySelectorAll('.lazy');
const observer = new IntersectionObserver((entries, observer) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const img = entry.target;
img.src = img.dataset.src;
img.classList.remove('lazy');
observer.unobserve(img);
}
});
});
lazyImages.forEach(img => {
observer.observe(img);
});
</script>
</body>
</html>
路由懒加载:按需加载代码,提升首屏速度
在单页面应用中,路由懒加载是一项重要的优化技术。它可以将不同路由的代码分割成独立的文件,只有当用户访问该路由时,才会加载相应的代码。这样可以显著减少首屏加载时间,提高用户体验。
javascript
javascript
// Vue 路由懒加载示例
const routes = [
{
path: '/about',
component: () => import('./views/About.vue')
}
];
资源预加载与 DNS 预解析:提前布局,加速资源加载
使用 <link rel="preload">
可以预加载重要的资源,而 <link rel="dns-prefetch">
则可以预解析 DNS,提前建立与服务器的连接,从而提高资源加载速度。
xml
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<link rel="preload" href="styles.css" as="style">
<link rel="dns-prefetch" href="//example.com">
</head>
<body>
<!-- 页面内容 -->
</body>
</html>
JS 执行优化
防抖与节流:控制函数执行频率的利器
防抖和节流是两种常用的优化技术,它们可以限制函数的执行频率,减少不必要的计算和资源消耗。在处理高频事件(如滚动、窗口大小改变等)时,这两种技术尤为有用。
ini
// 防抖函数
function debounce(func, delay) {
let timer;
return function () {
const context = this;
const args = arguments;
clearTimeout(timer);
timer = setTimeout(() => {
func.apply(context, args);
}, delay);
};
}
// 节流函数
function throttle(func, limit) {
let inThrottle;
return function () {
const context = this;
const args = arguments;
if (!inThrottle) {
func.apply(context, args);
inThrottle = true;
setTimeout(() => inThrottle = false, limit);
}
};
}
Web Worker:释放主线程,提升计算性能
Web Worker 是 HTML5 的一项重要特性,它允许我们在后台线程中执行 JavaScript 代码,从而避免阻塞主线程。这对于处理复杂的计算任务非常有用,可以显著提升页面的响应性能。
xml
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
</head>
<body>
<button id="startWorker">Start Worker</button>
<script>
const startWorkerButton = document.getElementById('startWorker');
startWorkerButton.addEventListener('click', () => {
if (typeof Worker !== 'undefined') {
const worker = new Worker('worker.js');
worker.onmessage = function (event) {
console.log('Received message from worker:', event.data);
};
worker.postMessage('Start calculation');
} else {
console.log('Web Workers are not supported in this browser.');
}
});
</script>
</body>
</html>
ini
// worker.js
onmessage = function (event) {
if (event.data === 'Start calculation') {
let result = 0;
for (let i = 0; i < 1000000; i++) {
result += i;
}
postMessage(result);
}
};
框架层面优化
React 的 useMemo
和 useCallback
:缓存计算结果,避免重复渲染
在 React 中,useMemo
和 useCallback
是两个非常实用的钩子函数。useMemo
可以缓存计算结果,避免在每次渲染时都进行重复计算;useCallback
则可以缓存函数,避免在每次渲染时都创建新的函数实例。
javascript
import React, { useMemo, useCallback } from 'react';
function App() {
const [count, setCount] = React.useState(0);
const expensiveCalculation = useMemo(() => {
let sum = 0;
for (let i = 0; i < 1000000; i++) {
sum += i;
}
return sum;
}, []);
const handleClick = useCallback(() => {
setCount(count + 1);
}, [count]);
return (
<div>
<p>Count: {count}</p>
<p>Expensive Calculation: {expensiveCalculation}</p>
<button onClick={handleClick}>Increment</button>
</div>
);
}
export default App;
使用 key
优化列表渲染:帮助框架高效识别元素变化
在 React 或 Vue 中,使用 key
可以帮助框架快速识别哪些元素发生了变化,从而优化列表的渲染性能。key
应该是唯一的,并且尽量保持稳定。
javascript
import React from 'react';
function List() {
const items = [1, 2, 3, 4, 5];
return (
<ul>
{items.map(item => (
<li key={item}>{item}</li>
))}
</ul>
);
}
export default List;
缓存策略
本地存储与会话存储:浏览器中的数据缓存方案
localStorage
和 sessionStorage
是浏览器提供的两种本地存储机制,它们可以用于在浏览器中存储数据,避免重复请求服务器。localStorage
中的数据会一直保留,直到手动清除;而 sessionStorage
中的数据会在会话结束时自动清除。
javascript
// 存储数据
localStorage.setItem('username', 'JohnDoe');
// 获取数据
const username = localStorage.getItem('username');
console.log(username);
强缓存与协商缓存:减轻服务器压力,加速页面加载
通过设置 HTTP 响应头,我们可以实现强缓存和协商缓存。强缓存可以让浏览器直接使用本地缓存的资源,而无需向服务器发送请求;协商缓存则需要向服务器发送一个请求,询问服务器该资源是否有更新,如果没有更新,则使用本地缓存。
ini
// Node.js 示例
const http = require('http');
const fs = require('fs');
const path = require('path');
const server = http.createServer((req, res) => {
const filePath = path.join(__dirname, 'public', req.url);
fs.stat(filePath, (err, stats) => {
if (err) {
res.statusCode = 404;
res.end('File not found');
} else {
const lastModified = new Date(stats.mtime).toUTCString();
const ifModifiedSince = req.headers['if-modified-since'];
if (ifModifiedSince === lastModified) {
res.statusCode = 304;
res.end();
} else {
res.setHeader('Last-Modified', lastModified);
res.setHeader('Cache-Control', 'max-age=3600'); // 强缓存 1 小时
fs.createReadStream(filePath).pipe(res);
}
}
});
});
server.listen(3000, () => {
console.log('Server is running on port 3000');
});
网络优化
CDN 加速:借助分布式网络,提升资源加载速度
CDN(内容分发网络)可以将静态资源分发到离用户最近的节点,从而减少网络延迟,提高资源加载速度。使用 CDN 可以显著提升页面的响应性能,尤其是对于全球范围内的用户。
xml
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/jquery.min.js"></script>
</head>
<body>
<!-- 页面内容 -->
</body>
</html>
Gzip 压缩:减少传输数据大小,提高网络效率
在服务器端开启 Gzip 压缩可以将传输的数据进行压缩,从而减少数据的大小,提高网络传输效率。这对于提升页面加载速度和节省带宽都非常有帮助。
ini
// Node.js 示例
const express = require('express');
const compression = require('compression');
const app = express();
app.use(compression());
app.get('/', (req, res) => {
res.send('Hello, World!');
});
app.listen(3000, () => {
console.log('Server is running on port 3000');
});
HTTP/2 多路复用:高效传输数据,提升大文件处理能力
HTTP/2 的多路复用特性允许在一个连接上同时传输多个请求和响应,这大大提高了大文件上传和下载的速度。与 HTTP/1.1 相比,HTTP/2 可以更高效地利用网络资源。
javascript
// Node.js 示例
const http2 = require('http2');
const fs = require('fs');
const server = http2.createSecureServer({
key: fs.readFileSync('server.key'),
cert: fs.readFileSync('server.crt')
});
server.on('stream', (stream, headers) => {
stream.respond({
'content-type': 'text/html',
':status': 200
});
stream.end('<h1>Hello, HTTP/2!</h1>');
});
server.listen(8443, () => {
console.log('Server is running on port 8443');
});
首屏优化
SSR(服务器端渲染):提前生成 HTML,加速首屏显示
SSR 可以在服务器端生成 HTML 内容,然后将其发送给浏览器。这样可以减少首屏加载时间,提高搜索引擎优化(SEO)效果。对于需要快速显示内容的页面,SSR 是一个非常有效的优化方案。
ini
// Node.js + React SSR 示例
const express = require('express');
const React = require('react');
const ReactDOMServer = require('react-dom/server');
const App = require('./App');
const app = express();
app.get('/', (req, res) => {
const html = ReactDOMServer.renderToString(<App />);
const page = `
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>SSR Example</title>
</head>
<body>
<div id="root">${html}</div>
<script src="client.js"></script>
</body>
</html>
`;
res.send(page);
});
app.listen(3000, () => {
console.log('Server is running on port 3000');
});
骨架屏:营造快速加载的视觉体验
骨架屏是一种在页面加载时显示的占位布局,它可以让用户感觉页面正在快速加载,从而提高用户体验。骨架屏通常使用简单的图形和动画来模拟页面的结构。
xml
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<style>
.skeleton {
background-color: #f0f0f0;
animation: skeleton-loading 1s infinite alternate;
}
@keyframes skeleton-loading {
0% {
opacity: 0.6;
}
100% {
opacity: 1;
}
}
</style>
</head>
<body>
<div class="skeleton" style="width: 200px; height: 20px;"></div>
<div class="skeleton" style="width: 150px; height: 20px; margin-top: 10px;"></div>
<script>
// 模拟数据加载
setTimeout(() => {
const skeletons = document.querySelectorAll('.skeleton');
skeletons.forEach(skeleton => {
skeleton.style.display = 'none';
});
// 显示真实内容
}, 2000);
</script>
</body>
</html>
首屏数据预加载:提前推送数据,减少用户等待
使用 HTTP/2 的 Server Push 可以在服务器端主动推送首屏所需的数据,这样可以减少用户的等待时间,提高页面的响应速度。
javascript
// Node.js 示例
const http2 = require('http2');
const fs = require('fs');
const server = http2.createSecureServer({
key: fs.readFileSync('server.key'),
cert: fs.readFileSync('server.crt')
});
server.on('stream', (stream, headers) => {
stream.pushStream({ ':path': '/styles.css' }, (pushStream) => {
pushStream.respondWithFile('styles.css', {
'content-type': 'text/css'
});
});
stream.respond({
'content-type': 'text/html',
':status': 200
});
stream.end('<html><head><link rel="stylesheet" href="styles.css"></head><body><h1>Hello, HTTP/2!</h1></body></html>');
});
server.listen(8443, () => {
console.log('Server is running on port 8443');
});
监控与分析
Performance API:精准测量页面性能指标
使用 Performance
API 可以测量页面的各种性能指标,如加载时间、渲染时间等。通过分析这些指标,我们可以找出页面性能的瓶颈,从而进行针对性的优化。
javascript
window.addEventListener('load', () => {
const performanceData = window.performance.timing;
const loadTime = performanceData.loadEventEnd - performanceData.navigationStart;
console.log(`Page loaded in ${loadTime} milliseconds`);
});
如果以上性能关于性能优化对于你有帮助的话,请给作者点个赞吧