要说面试的时候问到最多的问题,那性能优化绝对是躲不开的话题,基本每个公司面试我都遇到了相关问题,其中,首页加载速度优化又是其中最常问的问题,网上的文章比较零零散散,没有一个总结到十分满意的,于是自己便来总结一下
这张图是我发现的比较宝藏,比较全面的一张首页加载优化图,便以此图来进行相关总结
目录
一. 资源加载优化
- 压缩资源
- 启用Gzip压缩
- 使用缓存
- [使用内容分发网络 (CDN)](#使用内容分发网络 (CDN) "#%E4%BD%BF%E7%94%A8%E5%86%85%E5%AE%B9%E5%88%86%E5%8F%91%E7%BD%91%E7%BB%9C-cdn")
- 使用HTTP/2
- 优化DNS解析
- 减少HTTP请求数
- 预加载和预获取
- 使用更高效的图片格式
- 图片懒加载和路由懒加载
二. 页面渲染优化
- 优化CSS
- 使用CSS3动画代替JavaScript动画
- 将JavaScript放在页面底部
- 使用async和defer属性
- 减少和优化DOM操作
- [使用Virtual DOM](#使用Virtual DOM "#%E4%BD%BF%E7%94%A8virtual-dom")
- 避免布局抖动
- 使用will-change提示浏览器优化
- [使用服务端渲染 (SSR)](#使用服务端渲染 (SSR) "#%E4%BD%BF%E7%94%A8%E6%9C%8D%E5%8A%A1%E7%AB%AF%E6%B8%B2%E6%9F%93-ssr")
- 延迟加载资源
资源加载优化
压缩资源
通过 Webpack 配置,可以自动压缩 HTML、CSS 和 JavaScript 文件。
javascript
// webpack.config.js
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const TerserPlugin = require('terser-webpack-plugin');
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');
module.exports = {
mode: 'production',
entry: './src/index.js',
output: {
filename: 'bundle.js',
path: __dirname + '/dist'
},
module: {
rules: [
{
test: /\.css$/,
use: [MiniCssExtractPlugin.loader, 'css-loader']
}
]
},
optimization: {
minimize: true,
minimizer: [
new TerserPlugin(),
new CssMinimizerPlugin()
]
},
plugins: [
new HtmlWebpackPlugin({
template: './src/index.html',
minify: {
collapseWhitespace: true,
removeComments: true,
removeRedundantAttributes: true,
useShortDoctype: true
}
}),
new MiniCssExtractPlugin({
filename: 'styles.css'
})
]
};
启用Gzip压缩
通过 Webpack 配置,可以生成 Gzip 压缩文件。
js
//在请求时会带上该请求头,声明它支持的压缩算法
Accept-Encoding: gzip, deflate, br
javascript
// webpack.config.js
const CompressionPlugin = require('compression-webpack-plugin');
module.exports = {
// 其他配置...
plugins: [
new CompressionPlugin({
filename: '[path][base].gz',
algorithm: 'gzip',
test: /\.(js|css|html|svg)$/,
threshold: 10240,
minRatio: 0.8
})
]
};
使用缓存
服务端通过配置协商缓存和强缓存,来实现请求的缓存,这里以强缓存为例
javascript
// 设置强缓存
const express = require('express');
const path = require('path');
const app = express();
// 强缓存中间件
app.use((req, res, next) => {
const options = {
maxAge: '1y', // 缓存一年
immutable: true
};
// 设置 Cache-Control 头
res.set('Cache-Control', `public, max-age=${options.maxAge}, immutable`);
next();
});
// 将静态文件托管到 public 目录
app.use(express.static(path.join(__dirname, 'public'), {
maxAge: '1y' // 缓存一年
}));
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
console.log(`Server is running on port ${PORT}`);
});
使用内容分发网络 (CDN)
通过 Webpack 配置,将静态资源路径指向 CDN。
javascript
output: {
filename: '[name].[contenthash].js',
path: __dirname + '/dist',
publicPath: 'https://cdn.example.com/' // 指向你的 CDN 地址
},
使用HTTP/2
启用 HTTP/2 需要在服务器配置中完成,Webpack 本身不直接支持 HTTP/2 配置。
http
# Nginx 配置
server {
listen 443 ssl http2;
server_name example.com;
# SSL 配置
}
优化DNS解析
在 HTML 中添加 DNS 预获取。
html
<link rel="dns-prefetch" href="//example.com">
减少HTTP请求数
通过 Webpack 配置,合并文件和使用图片精灵,同时我们可以将一些小图片转为base64格式(虽然会减少请求,但是转为base64资源体积会变大一点,所有不推荐进行大图片base64处理)
javascript
// webpack.config.js
const SpriteLoaderPlugin = require('svg-sprite-loader/plugin');
module.exports = {
// 其他配置...
module: {
rules: [
{
test: /\.svg$/,
use: ['svg-sprite-loader']
}
]
},
plugins: [
new SpriteLoaderPlugin()
]
};
预加载和预获取
使用 Webpack 插件进行预加载和预获取。
javascript
// webpack.config.js
const PreloadWebpackPlugin = require('preload-webpack-plugin');
module.exports = {
// 其他配置...
plugins: [
new PreloadWebpackPlugin({
rel: 'preload',
as: 'script',
include: 'allChunks'
})
]
};
使用更高效的图片格式
通过 Webpack 配置,使用现代图片格式,如 WebP(但是得注意浏览器兼容性)。
javascript
// webpack.config.js
const ImageMinimizerPlugin = require('image-minimizer-webpack-plugin');
module.exports = {
// 其他配置...
module: {
rules: [
{
test: /\.(jpe?g|png|gif|svg)$/i,
type: 'asset',
use: [
{
loader: ImageMinimizerPlugin.loader,
options: {
minimizerOptions: {
plugins: [
['imagemin-webp', { quality: 75 }]
]
}
}
}
]
}
]
}
};
图片懒加载和路由懒加载
通过IntersectionObserver API和自定义指令来实现图片懒加载,路由懒加载即用vue的动态路由@import引入即可
javascript
export default {
inserted(el) {
const loadImage = () => {
const imageElement = el.tagName === 'IMG' ? el : el.querySelector('img');
if (imageElement) {
imageElement.src = imageElement.dataset.src;
imageElement.onload = () => el.classList.add('loaded');
}
};
const handleIntersect = (entries, observer) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
loadImage();
observer.unobserve(el);
}
});
};
const options = {
root: null,
threshold: 0.1
};
const observer = new IntersectionObserver(handleIntersect, options);
observer.observe(el);
}
};
页面渲染优化
优化CSS
将 CSS 外链放在页面顶部,因为这样可以确保页面在加载时尽快应用样式,从而避免样式闪烁(FOUC,Flash of Unstyled Content)并提升用户体验。
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Example Page</title>
<!-- 将 CSS 外链放在页面顶部 -->
<link rel="stylesheet" href="styles/main.css">
<link rel="stylesheet" href="styles/theme.css">
</head>
</html>
使用CSS3动画代替JavaScript动画
使用 CSS3 动画而不是 JavaScript 动画(原理: transform等css3属于是独立的图层,不会影响其他图层,而且使用GPU加速),以减少重排和重绘。
css
.box {
transition: transform 0.5s, opacity 0.5s;
}
.box:hover {
transform: translateX(100px);
opacity: 0.5;
}
将JavaScript放在页面底部
将 JavaScript 文件放在 <body>
标签的底部(现代浏览器支持async和defer后就不需要了)
html
<body>
<!-- Content -->
<script src="bundle.js"></script>
</body>
使用async和defer属性
使用 async
或 defer
属性加载外部 JavaScript 文件。
html
<script src="bundle.js" async></script>
<!-- 或者 -->
<script src="bundle.js" defer></script>
减少和优化DOM操作
减少不必要的 DOM 操作,合并多次操作为一次。
javascript
// Before
element.style.width = '100px';
element.style.height = '100px';
// After
element.style.cssText = 'width: 100px; height: 100px;';
使用Virtual DOM
使用虚拟 DOM 技术(如 React)减少直接操作 DOM 带来的开销。
javascript
// 使用 React 示例
import React, { useState } from 'react';
function App() {
const [count, setCount] = useState(0);
return (
<div>
<p>{count}</p>
<button onClick={() => setCount(count + 1)}>Increment</button>
</div>
);
}
避免布局抖动
在操作 DOM 和样式时,避免可能导致重排的操作,对样式进行统一处理
javascript
// Before
element.style.margin = '10px';
element.style.padding = '20px';
element.style.border = '1px solid #000';
// After
element.style.cssText = 'margin: 10px; padding: 20px; border: 1px solid #000;';
使用will-change提示浏览器优化
使用 will-change
属性可以提示浏览器即将发生的变化,使浏览器提前进行优化。
css
.box {
will-change: transform, opacity;
}
使用服务端渲染 (SSR)
使用服务端渲染技术提前生成 HTML 内容,减少客户端渲染的负担。
javascript
// 使用 Next.js 进行 SSR 示例
import React from 'react';
import { renderToString } from 'react-dom/server';
import App from './App';
const html = renderToString(<App />);
延迟加载资源
延迟加载图片和非关键 CSS、JavaScript 文件。
html
<img src="image.jpg" loading="lazy" alt="Lazy loaded image">
<script>
var link = document.createElement('link');
link.rel = 'stylesheet';
link.href = 'non-critical-styles.css';
document.head.appendChild(link);
</script>