前端性能优化——首页资源压缩 63%、白屏时间缩短 86% (opens new window)
从优化到面试装逼指南——网络系列 (opens new window)
# 懒加载
# 路由懒加载
# 组件懒加载
# 函数防抖 或 节流
减少请求次数;有些情况下可以减少复杂计算的执行次数
# CSS 优化
减少重排和重绘
什么属性会触发重排、重绘,参考 csstriggers (opens new window)
尽量使用 CSS 完成动画效果
优化 CSS 选择器:避免使用过于复杂的选择器,如通配符选择器、深层后代选择器等,因为它们会增加浏览器的计算负担,影响性能
提取关键 CSS:将对首次渲染最关键的 CSS 提取出来,并将其内联在 HTML 文档的
<head>部分,以减少外部样式表的加载时间
# 合理使用 Tree Shaking
打包时,清除未使用到的代码
# code splitting (代码分割)
加快首屏加载速度
# 使用更小、更轻便的库 替换掉一些大型库
如 dayjs 替换 moment.js
# 骨架屏优化白屏时长
# 长列表虚拟滚动
# Web Worker 优化长任务
# service worker
- 预加载数据
- 缓存数据
- 做网络降级 (离线)
# WebAssembly 优化计算密集型任务
将繁重的计算任务抽离到 WebAssembly(WASM)执行。对于大多数 Web 应用程序来说,JavaScript 更适合,而 WebAssembly 最适合用于计算密集型 Web 应用程序,例如 Web 游戏。
# requestAnimationFrame 制作动画 (而不是 setTimeout 或者 setInterval)
# 文件阻塞策略
# (1) 正常模式
正常的 <script> 的下载和执行都会阻塞页面的渲染。
# (2) async 模式
用在 script 标签,浏览器遇到 async 脚本时不会阻塞页面渲染,而是直接下载然后立马运行(运行会阻塞 html 的解析)。
<script async src="js/script.js"></script>
避免将 放在 async 代码段之前。如果脚本不依赖于样式表,可以考虑将阻塞脚本置于阻塞样式之上。如果存在依赖,可以将 JavaScript 分成两部分 (依赖 CSS 与不依赖 CSS),将它们分别放到 CSS 的两边来加载。
# (3) defer 模式
用在 script 标签,浏览器遇到 defer 脚本时不会阻塞页面渲染,而是先下载,然后等 html 解析完成后再运行。
<script defer src="js/script2.js"></script>
# (4) module 模式
# (5) preload (预加载)
关键字 preload 作为元素 <link> 的属性 rel 的值,表示用户十分有可能需要在当前浏览中加载目标资源,所以浏览器必须预先获取和缓存对应资源。preload 并不会阻塞 window 的 onload 事件。
as 属性可以说是必须要设置的,除了上面可以给优先级排级别以外,还有一个原因:如果不设置的话,它会被作为一个 XHR 请求去触发,浏览器可能不能正确的认识到,我们其实已经把资源预加载了,这样子就会加载两次了,完全没有了优化的效果。
as 属性用于规定资源的类型,并根据资源类型设置 Accept 请求头,以便能够使用正常的策略去请求对应的资源。as 的值可以取 style、script、image、font、fetch、document、audio、video 等。目前来说,除了 font,其他都按照和资源优先级相同的规则。
什么情况会导致二次获取?
- 不要将 preload 和 prefetch 进行混用,它们分别适用于不同的场景,对于同一个资源同时使用 preload 和 prefetch 会造成二次的下载。
- preload 字体不带 crossorigin 也将会二次获取! 确保你对 preload 的字体添加 crossorigin 属性,否则他会被下载两次,这个请求使用匿名的跨域模式。这个建议也适用于字体文件在相同域名下,也适用于其他域名的获取(比如说默认的异步获取)。
使用场景:
- 预加载定义在 CSS 中资源的下载,比如自定义字体
- 预加载 CSS 文件
- 结合媒体查询预加载响应式图片
- 结合 Webpack 预加载 JS 模块
# (6) prefetch (预获取)
要求浏览器请求一个资源
关键字 prefetch 作为元素 <link> 的属性 rel 的值,是为了提示浏览器,用户未来的浏览有可能需要加载目标资源,所以浏览器有可能通过事先获取和缓存对应资源,优化用户体验。
# (7) preconnect (预连接)
控制浏览器在后台启动连接握手(DNS, TCP, TLS)
向浏览器提供提示,建议它提前打开与链接网站的连接,而不透露任何私人信息或下载任何内容,以便在跟踪链接时能更快地获取链接内容。
# 首屏性能优化
# CSR + SSR
将一些客户端渲染移交到服务端。使用服务器端渲染来快速获得第一个有意义的图形,同时还包括一些最少的必需的 JavaScript,以使可交互时间紧挨着第一个有意义的图形的绘制。
# 延时渲染
延迟加载部分 UI,避免在用户真正需要它们之前因为加载、解析和编译造成的成本消耗
# 仅向需要的客户端返回 polyfills
使用 module/nomodule 模式。一个包含 Babel 转换和 polyfills,仅提供给实际需要它们的旧版浏览器,另一个包(相同功能)不包含 Babel 转换和 polyfills。
# 客户端预渲染
与服务器端预渲染相似,但不是在服务器上动态渲染页面,而是在构建时将应用程序渲染为静态 HTML。
# 给不同的资源设置优先级,控制它们的加载顺序
# 异步加载 JS,视频,iframe,小组件,图片等 (如:翻译字符串,表情符号等)
# 优先加载首屏 CSS,延迟加载其余的 CSS
# 使用 HTTP/2 的服务器推送提前返回文件 (Server Push)
如:可以将关键 CSS 存储在一个单独的 CSS 文件中,并通过服务器推送提前返回
# 启用事件委托 (事件代理)
利用事件冒泡机制将原本应该绑定在子元素上的事件全部交由父元素来完成。(绑定的事件越多,在内存中的占比就越大)
# 开发环境下
# 配置 include/exclude 缩小 Loader 对文件的搜索范围,避免不必要的转译
# 按需加载页面文件 (vite 的懒加载)
# 图片优化
# 图片压缩
# 借助 CDN 的压缩功能实现图片动态裁剪
很多云服务,比如阿里云或七牛云,都提供了图片的动态裁剪功能,经过动态裁剪后的图片,加载速度会有非常明显的提升
只需在图片的 url 地址上动态添加参数,就可以得到你所需要的尺寸大小,比如:http://7xkv1q.com1.z0.glb.clouddn.com/grape.jpg?imageView2/1/w/200/h/200 (opens new window)
在清晰度要求没那么高的情况下,可以获取较小尺寸的图片以减少图片加载事件。
# 使用 webp 格式的图片
webp 格式的图片比 png/jpg 有着更优秀的算法。在图片体积上会比 jpg/png 更小。所以加载的也就更快,耗费的带宽也就越少。占用加载资源的时间也就越短。
使用 WebP, 可通过 picture 元素和 JPEG 进行兜底。
# 图片懒加载
可借助 IntersectionObserver Api 实现图片进入用户浏览的窗口时再加载。
# 图片延时加载
为了缩短开始渲染关键图像所需时间,可延时加载不太重要的图片。
# 使用雪碧图或精灵图 (CSS sprites)
sprite 指的是精灵,我们喝的 雪碧 饮料也是 sprite
把一些小图片(比如图标)也都整合到一张大图片,然后你通过 background-position 等属性使用你需要的区域就好了
# 使用字体图标
(1) 轻量级:一个图标字体要比一系列的图像要小。一旦字体加载了,图标就会马上渲染出来,减少了 http 请求
(2) 灵活性:可以随意的改变颜色、产生阴影、透明效果、旋转等
(2) 兼容性:几乎支持所有的浏览器,请放心使用
# 图片转 base64 格式
将小图片转换为 base64 编码字符串,并写入 HTML 或者 CSS 中,减少 http 请求
转 base64 格式的优缺点:
(1) 它处理的往往是非常小的图片,因为 Base64 编码后,图片大小会膨胀为原文件的 4/3,如果对大图也使用 Base64 编码,后者的体积会明显增加,即便减少了 http 请求,也无法弥补这庞大的体积带来的性能开销,得不偿失
(2) 在传输非常小的图片的时候,Base64 带来的文件体积膨胀、以及浏览器解析 Base64 的时间开销,与它节省掉的 http 请求开销相比,可以忽略不计,这时候才能真正体现出它在性能方面的优势
项目可以借助 webpack、vite 的类似于 url-loader 的工具将图片转成 base64
# 渐进式加载图片
先加载低质量甚至模糊的图片,然后随着页面继续加载,将它们替换为高质量的完整版本。
# 资源文件优化
# GIF 替换为视频
将动画 GIF 替换为循环播放的内联视频是一个提高网页性能和用户体验的好方法,因为视频通常比 GIF 文件小,加载更快,并且可以提供更好的画质。
# 网络字体优化
1、网络字体很有可能会包含字形以及未使用的额外功能(如: 斜体)和粗细。借助工具可以将字体转换成较小的子集。
2、借助工具提取页面所需要的字体子集。
# 仅向旧版本浏览器提供兼容代码
仅在旧版浏览器中加载 polyfill
# 过滤掉未使用的 css
- 人工
- 借助工具 (浏览器开发者工具, 在线 CSS 简化工具, CSS 清理工具, 如 PurgeCSS)
- UnoCss (UnoCSS 通过其即时按需的原子化 CSS 引擎,有效地解决了 Tailwind CSS 在开发模式下按需加载的问题,提供了一种更灵活、可扩展且性能更高的 CSS 解决方案。)
- postcss 插件
# 缓存优化
# 强缓存
Expires:
如:Thu, 01 Dec 1994 16:00:00 GMT
表示资源的具体过期时间,过期了就得向服务端发请求,然而服务器资源和电脑本地时间不同步会导致缓存更新策略不一致,例如用户自己修改时间。
Cache-control:
指定从请求的时间开始,允许获取的响应被重用的最长时间。
例如,max-age=60 表示可在接下来的 60 秒缓存和重用响应。
若 Expires、 Cache-control 同时存在, Cache-Control 的 优先级更高
# 协商缓存
Last-Modified / If-Modified-Since
Etag / If-None-Match
# localStorage、sessionStorage
浏览器级别的本地存储,都存在于用户本地浏览器中
storage 的大小比 cookie 大得多(storage 为 5M,cookie4k),同时也是对应一个域名
# service worker 缓存
# 服务端开启 gzip 压缩
可有效的减小加载文件的大小
webpack 的 gzip 和 nginx 的有什么关系? 1.nginx 没有开启 gzip 压缩,webpack 打包出的.gz 文件是用不到的
2.nginx 开启了 gzip,nginx 查找静态资源是否存在已经压缩好的 gzip 压缩文件,如果没有则自行压缩(消耗 cpu 但感知比较少)
3.nginx 开启 gzip 压缩,webpack 打包出的.gz 文件被找到,则直接使用.gz 文件,减少了 nginx 压缩的性能损耗
# 网络优化
# 使用 CDN
CDN 系统能够实时地根据网络流量和各节点的连接和负载状况以及到用户的距离和响应时间等综合信息将用户的请求重新导向离用户最近的服务节点上,加快访问速度。
浏览器同一时间段对同一个域名请求文件的最大下载数是有限的,可以利用多域名来存放不同的静态资源,增大页面加载时资源的并行下载数,缩短页面资源加载的时间,通常把第三方库 (Vue 项目: vue, vue router, vuex, ElementUi, axios, Echarts 等) (React 项目: React, React Router, Ant Design, axios, Echarts 等)、图片等放在一个专用的 cdn 上就是一种解决方案。
# HTTP DNS
HTTP DNS 通过使用 HTTP、HTTPS 协议进行域名解析,提供了一种更安全、更快速、更精准的域名解析服务,尤其适用于移动应用和需要高安全性的场景。
# Edge workers
Edge Workers 技术是一种在边缘节点上运行的计算能力,它允许开发者在离用户更近的地方执行代码,从而减少延迟,提高性能。这种技术通常用于内容分发网络(CDN)和云服务提供商的平台中。
Edge Workers 实际上提供了一种在边缘节点上运行 JavaScript 函数的能力。
Edge Workers 可以在不同的事件阶段介入 HTTP 请求,例如在请求到达源服务器之前或响应返回给用户之前。这样,开发者可以自定义请求和响应的处理方式,实现如缓存、性能优化、安全增强等功能。
使用 Edge Workers 可以解决以下具体的技术问题:
页面加载速度优化
:
- 图片压缩和优化:在边缘节点上实时压缩和优化图片,减少传输数据量,加快页面加载速度。
- 异步加载脚本:在边缘节点上异步加载广告脚本和其他第三方资源,避免阻塞主线程,提高页面响应速度。
内容个性化
:
- 地理位置定制内容:根据用户的地理位置动态提供定制化内容,如不同货币价格或地域特定的促销活动。
- 用户代理检测:根据用户的设备类型和浏览器,提供优化的内容和布局。
A/B 测试
:
- 流量分配:在 CDN 层面实现 A/B 测试,根据用户的 IP 地址或其他标识符,动态地将流量分配给不同页面布局版本,从而实现实时数据收集和分析。
安全性增强
:
- 请求过滤和验证:在边缘节点上实现请求过滤和验证,阻止恶意请求和攻击,提高应用的安全性。
- 自定义错误响应:当后端服务器出现问题时,Edge Workers 可以返回自定义的错误页面,提升用户体验。
SEO 优化
:
- 结构化数据注入:在边缘节点上动态注入结构化数据标记,提升搜索引擎对页面内容的理解和排名。
- URL 重写:根据 SEO 需求动态重写 URL,优化搜索引擎抓取和索引。
缓存控制
:
- 智能缓存:根据请求的内容类型和用户行为,动态调整缓存策略,提高缓存命中率和内容交付速度。
- 缓存清除和更新:在边缘节点上实现精确的缓存清除和更新,确保用户获取最新的内容。
实时数据处理
:
- 数据预处理:在边缘节点上对传感器数据、用户行为数据等进行预处理,减少后端服务器的负载。
- 日志收集和分析:实时收集和分析边缘节点的日志数据,快速发现和解决问题。
通过这些应用,Edge Workers 可以显著提升应用的性能、安全性和用户体验,同时降低带宽和服务器成本。
# 将不同类型的请求分发到不同的地址
由于浏览器会限制并发数,可以让不同类型的文件访问不同的地址
如,a 地址: 请求后端接口,b 地址:请求 html,css,js 文件,c 地址:请求图片视频文件
# dns-prefetch
可以在后台执行 DNS 查找
<link rel="dns-prefetch" href="//baidu.com" />
dns-prefetch 是尝试在请求资源之前解析域名。这可能是后面要加载的文件,也可能是用户尝试打开的链接目标。
dns-prefetch 仅对跨域的 DNS 查找有效,因此请避免使用它来指向您的站点或域。这是因为,到浏览器看到提示时,您站点域背后的 IP 已经被解析。
当浏览器从(第三方)服务器请求资源时,必须先将该跨域域名解析为 IP 地址,然后浏览器才能发出请求。此过程称为 DNS 解析。DNS 缓存可以帮助减少此延迟,而 DNS 解析可以导致请求增加明显的延迟。对于打开了与许多第三方的连接的网站,此延迟可能会大大降低加载性能。
需要注意的是,虽然使用 DNS Prefetch 能够加快页面的解析速度,但是也不能滥用,因为有开发者指出 禁用 DNS 预读取能节省每月 100 亿的 DNS 查询 。 要禁止隐式的 DNS Prefetch:
<meta http-equiv="x-dns-prefetch-control" content="off">
# 合并请求
在未启用 http2 的情况下。每次请求都需要进行 TCP 的三次握手和四次挥手,解析报文等一系列的过程,这些过程都需要时间去执行。并且,浏览器在同一域名下的请求并发数有限制,同一域名下同一个请求只能并发一个,不同类型请求(比如 GET/POST)并发个数基本在 4-6 个之间。假设当前浏览器的并发请求有 6 个。那么第 7 个请求就需要等前 6 个请求中任意一个完成以后才可以从任务队列中被拉出去执行。所以,合并请求可以在一定程度上减少资源响应时间,给用户带来更好的使用体验。
可采取的办法:
- css sprite
- base64 图片
- iconfont
- JS,CSS 文件合并
- 多个文件 combo 请求(使用?或者;分割要请求的多个文件)
Combo request 的过程涉及将多个资源文件(如 JavaScript 或 CSS 文件)合并成一个单一的 HTTP 请求,以减少浏览器与服务器之间的通信次数,从而提高页面加载速度。
有的 CDN 就支持 combo request 技术。例如,淘宝的 CDN 就使用了 combo 技术,通过特定的 URL 格式来实现资源的合并请求。
- 缓存请求结果
# 减少发送的请求包大小
- 减少 Cookie
策略: 主站首页设置白名单,定期删除非白名单 Cookie
好处: 减少页面间传输大小,对 Cookie 进行有效管理
- HTTP2, 头部压缩 (HPACK 压缩)
需要用代理工具看下请求头、响应头验证一下
# 使用 http2
在 HTTP2 中,强制使用 SSL/TLS。在 RFC 中并没有强制,其允许不加密的 HTTP/2,但是当前所有实现 HTTP/2 的 Web 浏览器都只支持加密 (SSL/TLS) 的 HTTP/2。
- 多路复用
- 头部压缩 (HPACK 压缩)
- 服务端推送 (Server Push)
# Server Push (服务器推送)
服务端可以在发送页面 HTML 时主动推送其它资源,而不用等到浏览器解析到相应位置,发起请求再响应
location / {
root /usr/share/nginx/html;
index index.html index.htm;
http2_push /Home.css; // 推送css
http2_push /Home.png; // 推送图片
}
# HTTP Cache-Control: max-age=xxx, stale-while-revalidate=xxx
这个指令允许客户端在资源过期之后,仍然使用过期的缓存数据,同时在后台异步地请求新的资源来更新缓存。这样做的好处是,即使缓存的数据已经过期,用户仍然可以立即访问到这些数据,而不需要等待新的资源从服务器加载,从而提高了用户体验和性能。
具体来说,stale-while-revalidate 通常与 max-age 指令一起使用。max-age 指定了资源在多久之后会被认为是过期的,而 stale-while-revalidate 则指定了在资源过期后,客户端愿意等待多久来使用过期的缓存数据,同时异步地请求新的资源。
例如,如果一个资源的 Cache-Control 头部设置为 Cache-Control: max-age=600, stale-while-revalidate=30,这意味着:
在资源加载后的 600 秒内,如果再次请求该资源,浏览器会直接从缓存中提供资源。 在 600 秒到 630 秒之间,如果请求该资源,浏览器会提供过期的缓存资源,同时在后台异步地请求新的资源。 如果超过 630 秒后再次请求资源,浏览器会等待新的资源从服务器加载,因为此时过期的缓存数据已经超过了 stale-while-revalidate 指定的时间窗口。
这种缓存策略特别适用于那些更新频率不是非常高,但用户希望快速访问数据的场景。它可以帮助减少服务器的负载,同时确保用户能够快速获取数据,即使这些数据不是最新的。然而,需要注意的是,并非所有的浏览器都支持 stale-while-revalidate,因此在实施这种策略时需要考虑浏览器的兼容性。
# SSR (服务端渲染)
服务端渲染(SSR)是一种在服务器端生成 HTML 的网页渲染技术。与传统的客户端渲染(CSR)相比,SSR 能够显著提升首屏加载速度、改善搜索引擎优化(SEO)以及提高用户体验。
# SSR 的工作原理
在 SSR 中,当用户请求一个页面时,服务器会根据请求的数据动态生成 HTML 内容,然后将这个完整的 HTML 页面发送给客户端。客户端浏览器直接接收到一个完整的 HTML 页面,而不需要等待 JavaScript 的下载和执行,这样可以减少首次加载的时间。
# SSR 的优势
- 更快的首屏加载:由于 HTML 内容是在服务器端生成的,用户可以更快地看到完整的页面,特别是在网络环境不佳或设备性能较低的情况下更为明显。
- 改善 SEO:搜索引擎爬虫可以直接看到完全渲染的页面,这有助于提高网站的索引效率,尤其是在内容的即时性非常重要的场景下。
- 提高用户体验:更快的加载时间和更好的 SEO 可以提升用户体验,减少用户的等待时间,提高网站的可访问性。
# SSR 的实现方式
实现 SSR 通常需要使用支持 SSR 的框架,例如 Next.js(React)、Nuxt.js(Vue.js)、Angular Universal(Angular)等。这些框架提供了在服务器端渲染应用的 API 和工具。此外,还可以使用服务器端渲染引擎,如 Node.js 的 Express、Koa 等,来处理页面请求并生成 HTML。
# SSR 的适用场景
- 内容密集型页面:对于需要大量内容渲染的页面,如新闻站点或博客,SSR 特别有用,因为它可以加速内容的加载。
- SEO 敏感性:如果网站对 SEO 非常敏感,例如电子商务网站,采用 SSR 可以提高搜索引擎的索引效率。
- 首屏渲染速度要求高:对于那些要求页面快速加载并具备良好用户体验的应用,SSR 可以降低首屏渲染的时间。
# SSR 的权衡
虽然 SSR 有许多优势,但在使用时也需要考虑一些权衡:
- 开发限制:浏览器端特定的代码只能在某些生命周期钩子中使用;一些外部库可能需要特殊处理才能在服务端渲染的应用中运行。
- 构建配置和部署要求:服务端渲染的应用需要一个能让 Node.js 服务器运行的环境,不像完全静态的 SPA 那样可以部署在任意的静态文件服务器上。
- 更高的服务端负载:在 Node.js 中渲染一个完整的应用要比仅仅托管静态文件更加占用 CPU 资源,因此如果预期有高流量,需要为相应的服务器负载做好准备,并采用合理的缓存策略。
在决定是否使用 SSR 时,应该根据应用的特定需求和场景来权衡这些因素。如果首屏加载速度对用户体验至关重要,SSR 可以是一个有效的解决方案。