性能优化 一直是前端绕不开的话题。页面加载慢、交互卡顿,不仅影响用户体验,还可能直接流失用户。本文将从加载、运行、构建、网络四个环节,系统梳理前端能想到的各种性能优化手段,帮助我们尽可能的提升前端页面性能。
加载性能优化-更快呈现首屏
加载阶段的目标是尽快把可见内容展示给用户,减少白屏和首屏等待时间。
资源压缩和代码混淆
资源压缩 的核心目标就是------让浏览器传输和解析的文件尽可能小,这样加载速度自然就快了。
- 代码压缩 :通过移除 HTML、CSS、JS 中的空格、注释,并缩短变量名来减小文件体积。打包阶段可借助 Vite 、Webpack 等构建工具内置或插件化的压缩方案如 Terser自动完成。
- 图片优化 :优先使用
WebP或 AVIF 等高压缩比格式,并通过 imagemin 、tinypng 等工具进一步压缩体积;对于大量小图标,可使用CSS Sprites合并成一张精灵图,减少 HTTP 请求数量。
代码分割
代码分割就是将项目代码或页面拆成多个小文件,用户访问时只加载当前需要的部分,如路由懒加载:
vue:
const routes = [
{ path: '/', component: () => import('@/views/Home.vue') },
{ path: '/about', component: () => import('@/views/About.vue') }
];
react:
import React, { Suspense } from 'react';
const Chart = React.lazy(() => import('./Chart'));
<Suspense fallback={<div>Loading...</div>}>
<Chart />
</Suspense>
Tree Shaking 摇树优化
Tree shaking 是一种在打包阶段自动删除未使用代码的优化技术,能让最终文件更小,加载更快,它依赖 ES Module(import / export)静态结构来分析哪些代码实际被用到,没用到就会被'摇掉'。
在vite 和大多数现代构建工具里,Tree Shaking 在生产构建时是默认开启的 只需要:
- 使用 ES Module语法,而不是require。
- 避免全局副作用代码,或在package.json 中声明;
javascript
{ "sideEffects": false }
CDN加速
CDN加速就是把网站的静态资源(js、css、图片、字体等)分发到全球多个节点,让用户就近从最近的服务器获取资源,从而减少网络延迟、提高加载速度。
项目中,可以将静态资源(js、css、图片、字体)部署到阿里云或腾讯云等CDN,让用户从最近的节点获取资源。
减少渲染阻塞
渲染阻塞是指浏览器在解析 HTML 时,遇到某些资源(如 CSS、同步 JS)会暂停页面渲染,直到这些资源加载并解析完成,这会直接延迟首屏显示时间。减少渲染阻塞的核心,就是让关键内容先呈现,非关键资源延后或异步加载。
1:CSS 优化 :将首屏必需的 CSS 抽取为关键 CSS直接内联到 HTML,其余样式文件通过 media 属性或延迟加载方式引入。
javascript
<link rel="stylesheet" href="style.css" media="print" onload="this.media='all'">
2:JS 优化 :对非首屏必须执行的 JS 使用 defer 或 async,避免阻塞 HTML 解析。
javascript
<script src="app.js" defer></script>
<script src="analytics.js" async></script>
3:字体加载优化 :使用 font-display: swap,让文字在字体加载前先用系统字体渲染,避免长时间空白。
javascript
@font-face {
font-family: 'MyFont';
src: url('myfont.woff2') format('woff2');
font-display: swap;
}
预加载和预渲染
预加载与预渲染的目标是提前把可能需要的资源或页面准备好,让用户在点击或访问时几乎无等待。
- preload:提前加载关键资源(如字体、CSS、首屏图片),确保它们在渲染时已经准备就绪。
javascript
<link rel="preload" href="/fonts/main.woff2" as="font" type="font/woff2" crossorigin>
- prefetch:利用浏览器空闲时间加载未来可能使用的资源(如下一页的 JS 文件),等真正用到时直接从缓存读取。
javascript
<link rel="prefetch" href="/next-page.js">
SSR 和 SSG
这两种方式都是在用户请求到达前,就把页面 HTML 准备好,从而减少白屏时间。
- SSR(Server-Side Rendering) :由服务端实时生成 HTML 并返回给浏览器,用户首屏几乎立刻可见,后续由前端接管交互。适合需要动态数据的场景,比如电商、资讯类网站。react中我们可以借助
next.js实现这一需求。 - SSG(Static Site Generation) :在构建阶段一次性生成所有静态 HTML 文件,用户访问时直接从服务器或 CDN 获取,速度极快且可离线缓存。适合内容更新不频繁的站点,比如博客、文档站。React 中可以使用 Astro 或 Next.js SSG 模式,Vue 生态中则有 VitePress 和 VuePress 等优秀工具。
依赖共享
在多页面应用(MPA)或微前端场景中,把公共依赖(如 React、Vue、Lodash 等)提取出来,通过 浏览器缓存 或 CDN 共享加载,可以避免重复下载同一依赖,减少首屏加载体积。
- Webpack 中可通过
SplitChunksPlugin配置vendor包。 - Vite 中可利用
optimizeDeps或manualChunks手动拆分依赖。
运行阶段优化
运行阶段的目标是让页面在交互过程中保持流畅不卡顿,通过优化渲染策略和代码,可以有效减少性能浪费。
避免不必要的重绘和回流
- 回流(Reflow) :当元素的大小、位置、布局发生变化时,浏览器需要重新计算布局,并重新渲染页面。
- 重绘(Repaint) :当元素外观(如颜色、背景)改变但布局没变时,只需要重新渲染外观
回流是性能杀手,它会引发页面重新计算布局,尤其是在复杂 DOM 结构下,代价非常高。重绘成本低一些,但频繁发生也会卡顿。
防抖与节流
防抖 和 节流 都是用来优化高频事件触发的技术,但原理和应用场景不同:
- 防抖(Debounce)
在事件频繁触发时,只在最后一次触发后 等待一段时间 才执行回调。如果在等待时间内事件又被触发,就重新计时。
适用场景:搜索框输入、窗口大小调整(resize)、表单实时验证等。
- 节流(Throttle)
在事件频繁触发时,保证 固定时间间隔 内只执行一次回调,即使事件被多次触发也不会更快执行。
适用场景:滚动(scroll)、拖拽(drag)、鼠标移动(mousemove)等。
图片懒加载
在网页加载时,只加载首屏或当前可见区域内的图片,其他图片等用户滚动到可见区域时再加载。这种方式称为图片懒加载,它有以下优点:
- 减少首屏加载时间,提升页面打开速度
- 降低首屏网络请求数量,节省带宽
- 减轻服务器瞬时压力
在HTML 中,原生懒加载写法如下:
javascript
<img src="image.jpg" loading="lazy" alt="example" />
当然,社区也有对应的开源库,如React的react-lazyload,vue的vue-lazyload。
Web Worker 分担计算压力
JavaScript 是单线程运行的,如果在主线程执行复杂计算(如文件解析、加密、压缩),会阻塞 UI 渲染。
Web Worker 允许我们在浏览器中开启一个独立的线程来执行 JavaScript 代码,把耗时、计算量大的任务放在这个线程中执行。
它的常见用途包括:
- 大量数据计算(加密、解密、数据分析)
- 图片、视频的压缩与处理
- 大文件解析(CSV、JSON)
- 实时数据流处理
注意,它依旧不是传统意义上的多线程。
使用案例:
worker.js
javascript
self.onmessage = e => {
let sum = 0;
for (let i = 0; i < e.data; i++) sum += i;
self.postMessage(sum);
};
App.jsx
javascript
import React from 'react';
export default function App() {
const runWorker = () => {
const worker = new Worker(new URL('./worker.js', import.meta.url));
worker.postMessage(1e8); // 计算 1 亿次
worker.onmessage = e => {
alert(`结果: ${e.data}`);
worker.terminate();
};
};
return <button onClick={runWorker}>开始计算</button>;
}
内存泄漏监控与优化
内存泄漏会让网页在长时间运行后越来越卡,最终崩溃。常见原因有:
- 未清理的定时器 / 事件监听
- 被引用的 DOM 节点未释放
- 闭包中保留了不必要的变量
优化手段:
- 使用
Chrome Performance工具分析内存快照 - 组件卸载时(React
useEffect返回清理函数 / VuebeforeUnmount)释放资源
构建优化
压缩与混淆
在 React/Vue 等前端项目里,压缩与混淆 基本都是构建工具自动完成的,你几乎不需要手动去配置。
网络优化
TCP 预连接
提前与目标服务器建立 TCP + TLS 连接,减少请求延迟。
javascript
<link rel="preconnect" href="https://cdn.example.com">
DNS 预解析
提前解析域名,减少 DNS 查询延迟。
javascript
<link rel="dns-prefetch" href="//cdn.example.com">
请求合并
对多个重复请求进行合并处理,前端可以通过防抖或判断接口状态实现。
javascript
<script setup>
import { ref } from 'vue';
import axios from 'axios';
const timeoutId = ref(null);
function debounce(fn, delay) {
return function(...args) {
if (timeoutId.value) clearTimeout(timeoutId.value);
timeoutId.value = setTimeout(() => {
fn(...args);
}, delay);
};
}
function fetchData() {
axios.get('http://api/gcshi) // 使用示例API
.then(response => {
console.log(response.data);
})
}
const debouncedFetchData = debounce(fetchData, 300);
</script>