原创 石小石Orz 2025-08-31 09:04 重庆
性能优化一直是前端绕不开的话题。页面加载慢、交互卡顿,不仅影响用户体验,还可能直接流失用户。本文将从加载、运行、构建、网络四个环节,系统梳理前端能想到的各种性能优化手段,帮助我们尽可能的提升前端页面性能。
性能优化一直是前端绕不开的话题。页面加载慢、交互卡顿,不仅影响用户体验,还可能直接流失用户。本文将从「加载、运行、构建、网络」四个环节,系统梳理前端能想到的各种性能优化手段,帮助我们尽可能的提升前端页面性能。加载性能优化:更快呈现首屏加载阶段的目标是「尽快把可见内容展示给用户」,减少白屏和首屏等待时间。资源压缩与代码混淆「资源压缩」的核心目标就是——「让浏览器传输和解析的文件尽可能小」,这样加载速度自然就快了。「代码压缩」:通过移除 HTML、CSS、JS 中的空格、注释,并缩短变量名来减小文件体积。打包阶段可借助 「Vite」、「Webpack」 等构建工具内置或插件化的压缩方案如 Terser 自动完成。「图片优化」:优先使用
WebP 或 「AVIF」 等高压缩比格式,并通过 「imagemin」、「tinypng」 等工具进一步压缩体积;对于大量小图标,可使用 CSS Sprites 合并成一张精灵图,减少 HTTP 请求数量。代码分割(Code Splitting)代码分割就是把项目代码按功能或页面拆成多个小文件,用户访问时「只加载当前需要的部分」,如路由懒加载:「React」:「Vue」:import React, { Suspense } from 'react';const Chart = React.lazy(() => import('./Chart'));<Suspense fallback={<div>Loading...</div>}><Chart /></Suspense>
Tree Shaking 摇树优化「Tree Shaking」 是一种在打包阶段自动删除未使用代码的优化技术,能让最终文件更小、加载更快。它依赖 「ES Module」 (const routes = [{ path: '/', component: () => import('@/views/Home.vue') },{ path: '/about', component: () => import('@/views/About.vue') }];
import/export) 的静态结构来分析哪些代码实际被用到,没用到的就会被 “摇掉”。在 「Vite」(基于 Rollup)和大多数现代构建工具里,Tree Shaking 在「生产构建」时是默认开启的,只需要:使用 ES Module 语法,而不是 require。避免全局副作用代码,或在 package.json 中声明:{ "sideEffects": false }CDN 加速「CDN 加速」就是把网站的静态资源(JS、CSS、图片、字体等)分发到全球多个节点,让用户就近从最近的服务器获取资源,从而减少网络延迟、提高加载速度。项目中,可以将静态资源(JS、CSS、图片、字体)部署到 阿里云或腾讯云等 CDN,让用户从最近的节点获取资源。减少渲染阻塞渲染阻塞是指浏览器在解析 HTML 时,遇到某些资源(如 CSS、同步 JS)会暂停页面渲染,直到这些资源加载并解析完成,这会直接延迟首屏显示时间。减少渲染阻塞的核心,就是让关键内容先呈现,非关键资源延后或异步加载。「CSS 优化」:将首屏必需的 CSS 抽取为关键 CSS 直接内联到 HTML,其余样式文件通过 media 属性或延迟加载方式引入。<link rel="stylesheet" href="style.css" media="print" onload="this.media='all'">「JS 优化」:对非首屏必须执行的 JS 使用 defer 或 async,避免阻塞 HTML 解析。「字体加载优化」:使用<script src="app.js" defer></script><script src="analytics.js" async></script>
font-display: swap,让文字在字体加载前先用系统字体渲染,避免长时间空白。预加载与预渲染预加载与预渲染的目标是提前把可能需要的资源或页面准备好,让用户在点击或访问时几乎无等待。「preload」:提前加载关键资源(如字体、CSS、首屏图片),确保它们在渲染时已经准备就绪。@font-face {font-family: 'MyFont';src: url('myfont.woff2') format('woff2');font-display: swap;}
<link rel="preload" href="/fonts/main.woff2" as="font" type="font/woff2" crossorigin>「prefetch」:利用浏览器空闲时间加载未来可能使用的资源(如下一页的 JS 文件),等真正用到时直接从缓存读取。<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 等优秀工具。Gzip/Brotli 压缩在服务器端启用 Gzip 或 Brotli 压缩,可以显著减小传输文件的体积,尤其是 JS、CSS、HTML 等文本类资源,通常能减少 60%~80% 的网络传输量。开启 Gzip 压缩,只需要在 vite 或 webapck 中开启配置,并在 nginx 中配置即可。依赖共享在多页面应用(MPA)或微前端场景中,把公共依赖(如 React、Vue、Lodash 等)提取出来,通过 浏览器缓存 或 CDN 共享加载,可以避免重复下载同一依赖,减少首屏加载体积。Webpack 中可通过 SplitChunksPlugin 配置 vendor 包。Vite 中可利用 optimizeDeps 或 manualChunks 手动拆分依赖。运行阶段优化运行阶段的目标是让页面在交互过程中保持流畅不卡顿,通过优化渲染策略和代码,可以有效减少性能浪费。避免不必要的重绘与回流「回流(Reflow)」 :当元素的大小、位置、布局发生变化时,浏览器需要重新计算布局,并重新渲染页面。「重绘(Repaint)」 :当元素外观(如颜色、背景)改变但布局没变时,只需要重新渲染外观回流是性能杀手,它会引发页面重新计算布局,尤其是在复杂 DOM 结构下,代价非常高。重绘成本低一些,但频繁发生也会卡顿。虚拟滚动 / 列表当你要渲染一个 1 万行的长表格,如果一次性全渲染,浏览器直接卡到怀疑人生。「虚拟列表的思路」其实很简单只渲染当前可见区域的内容,滚动时替换 DOM 节点,保证 DOM 数量稳定。Vue 和 React 也有很多开源库可以使用。防抖与节流「防抖」 和 「节流」 都是用来优化高频事件触发的技术,但原理和应用场景不同:「防抖(Debounce)」在事件频繁触发时,只在最后一次触发后 「等待一段时间」 才执行回调。如果在等待时间内事件又被触发,就重新计时。「适用场景」:搜索框输入、窗口大小调整(resize)、表单实时验证等。「节流(Throttle)」在事件频繁触发时,保证 「固定时间间隔」 内只执行一次回调,即使事件被多次触发也不会更快执行。「适用场景」:滚动(scroll)、拖拽(drag)、鼠标移动(mousemove)等。图片懒加载在网页加载时,只加载首屏或当前可见区域内的图片,其他图片等用户滚动到可见区域时再加载。这种方式称为图片懒加载,它有以下优点:减少首屏加载时间,提升页面打开速度降低首屏网络请求数量,节省带宽减轻服务器瞬时压力在 HTML 中,原生懒加载写法如下:<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」「📄」「App.jsx」self.onmessage = e => {let sum = 0;for (let i = 0; i < e.data; i++) sum += i;self.postMessage(sum);};
内存泄漏监控与优化「内存泄漏会让网页在长时间运行后越来越卡,最终崩溃。」常见原因有:未清理的定时器 / 事件监听被引用的 DOM 节点未释放闭包中保留了不必要的变量优化手段:使用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>;}
Chrome Performance 工具分析内存快照组件卸载时(React useEffect 返回清理函数 / Vue beforeUnmount)释放资源构建优化压缩与混淆在 React/Vue 等前端项目里,「压缩与混淆」基本都是「构建工具自动完成」的,你几乎不需要手动去配置。第三方库优化分包策略分包策略是指将打包后的代码分成多个 bundle,避免一次性加载所有资源,提高首屏速度。常见策略如「按路由分包」、「按组件分包」、「按依赖分包」。它能延迟非必要资源加载,提升首屏加载速度。被分包的依赖,如第三方库,打包后 hash 值不变,重新部署会使用缓存文件,也能提升首屏加载速度。在 Vite 中,使用它也很简单:懒加载第三方资源这种方式类似路由懒加载,延迟非关键资源的下载,按需加载第三方库或模块,避免在初始加载时引入全部依赖,减轻首页负担。// Vite Rollup 分包配置export default {build: {rollupOptions: {output: {manualChunks: {react: ['react', 'react-dom'], // 单独打包 reactlodash: ['lodash'] // lodash 单独打包}}}}}
依赖排除构建时将某些依赖排除,不打包进 bundle,而是从 CDN 加载。它可以有效减少 bundle 体积,利用缓存和边缘节点加速首页访问。但并非依赖排除的越多越好,js 请求也需要网络。如:// React 动态导入const Chart = React.lazy(() => import('./Chart'));export default function Page() {return (<React.Suspense fallback={<div>Loading...</div>}><Chart /></React.Suspense>);}
网络优化TCP 预连接提前与目标服务器建立 TCP + TLS 连接,减少请求延迟。// Vite 配置export default {build: {rollupOptions: {external: ['vue'], // 排除 vue}}}
<link rel="preconnect" href="https://cdn.example.com">DNS 预解析提前解析域名,减少 DNS 查询延迟。<link rel="dns-prefetch" href="//cdn.example.com">请求合并对多个重复请求进行合并处理,前端可以通过防抖或判断接口状态实现。如何查看网页性能浏览器内置工具「网络面板」查看网络请求,查看所有请求的耗时、大小、缓存命中情况,找出大文件、重复请求、慢响应资源。「性能面板」FCP(First Contentful Paint,首次内容绘制时间)LCP(Largest Contentful Paint,最大内容绘制时间)CLS(Cumulative Layout Shift,累计布局偏移)TTI(Time to Interactive,可交互时间)「Lighthouse 面板」自动化检测性能、可访问性、SEO 等综合评分,并给出优化建议。第三方平台工具WebPageTestURL:https://www.webpagetest.org/模拟不同地区、网络条件下的页面加载,查看瀑布图、渲染时间线。GTmetrixURL:https://gtmetrix.com/类似 Lighthouse,但报告更细,可看首屏截图、视频回放,方便对比优化前后效果。性能监控与上报「Sentry」:可采集性能、JS 错误、慢接口「阿里云 ARMS」 / 「字节火山监控」:支持前端 + 后端链路追踪「自建埋点系统」:结合 Performance API,将指标上报到日志系统总结本文梳理了一些比较常见的前端可行性能优化方案,有遗漏的地方,欢迎大家补充。<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>
AI编程资讯AI Coding专区指南:https://aicoding.juejin.cn/aicoding
点击"阅读原文"了解详情~
