前言
如今 web 的趋势:
- 更大更多的数据对象
- 更复杂的页面逻辑
- 更多的域名
- 更多的TCP连接
性能优化的正确思路:
- 理解浏览器的工作原理,认识网站性能影响的因素;
- 借助 PageInsight 等工具查看获得性能测试报告;
- 借助 Chrome Devtools 寻找性能的突破口;
- 使用正确的方法解决性能问题。
一、认识 cpu、线程 与 进程
chrome 浏览器最初是单进程架构,浏览器所有的线程都运行在这一个进程里面。它缺点很明显就是:进程中某个线程出了问题或者渲染较慢,就会导致整个进程出问题或拖慢渲染速度。
后来 chrome 浏览器演变成了现在这样的多进程架构,让不同的进程负责管理不同的模块任务,这样最大的好处是:让任务相互隔离,不会相互影响,从而保障了浏览器的运行稳定性。
cpu、进程和线程之间的关系:
- CPU:是对进程进行调度的一个最小单位。
- 进程:是系统进行资源分配和调度的基本单位。
- 线程:是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。
- cpu 的调度,使得进程之间处于相互隔离的状态,从而保障了应用运行的稳定性。
- 一个进程可以有很多线程,每条线程并行执行不同的任务,多个线程之间共享一个进程的总内存。
在 chrome 浏览器中,每个窗口页面就是一个渲染进程。这就保障了每个页面之间是相互隔离的,内存互不影响,即使某个页面崩了,其他页面也不受影响。
二、Web 前端性能优化指标
- FP(First Paint):首次绘制时间,这个指标用于记录页面第一次绘制像素的时间。
- FCP(First Contentful Paint):首次内容绘制时间,这个指标用于记录页面首次绘制文本、图片、非空白 Canvas 或 SVG 的时间。
- LCP(Largest Contentful Paint):最大内容绘制时间,用于记录视窗内最大的元素绘制的时间,该时间会随着页面渲染变化而变化,因为页面中的最大元素在渲染过程中可能会发生改变,另外该指标会在用户第一次交互后停止记录。
- CLS(Cumulative Layout Shift):累计位移偏移,记录了页面上非预期的位移波动。计算方式为:位移影响的面积 * 位移距离。
- TTI(Time to Interactive):首次可交互时间。这个指标计算过程略微复杂,它需要满足以下几个条件:
- 从 FCP 指标后开始计算;
- 持续 5 秒内无长任务(执行时间超过 50 ms)且无两个以上正在进行中的 GET 请求;
- 往前回溯至 5 秒前的最后一个长任务结束的时间。
- FID(First Input Delay):首次输入延迟时间,记录在 FCP 和 TTI 之间用户首次与页面交互时响应的延迟。
- TBT(Total Blocking Time):阻塞总时间,记录在 FCP 到 TTI 之间所有长任务的阻塞时间总和。
三、从 浏览器 的角度看 前端优化
前端性能优化指的是:用户开始访问我们的网站到整个页面完整展现出来的过程中,通过各种优化策略和方法,让页面加载的更快,让用户的操作响应更及时,给用户更好的使用体验。
(一)、影响性能的关键因素
影响性能的关键因素就在浏览器的工作流程里。浏览器的工作原理 - Chrome 浏览器运行机制
影响性能的因素:
- 延迟 / RTT
- 带宽:下载文件的大小 / 当时的带宽 = 下载所需要的时间
- DNS 解析时间:将域名转化为 IP 地址的过程所花费的时间。
- TCP 握手时间,若是 HTTPS 还有 SSL 握手时间。
- 静态资源:webpack。
(二)、性能测试
性能测试:是性能优化的基石。
- 实验室测试
- 真是用户测试
Google 推荐的性能测试工具:
- PageSpeed Insights
- Chrome User Experience Report
- Google Search Console Speed Report
- Fireperf
- Audit Panel
- Performance Budget
- Profile Panel
- Lighthouse CI
一切优化都要基于性能分析结果。借助性能测试的工具,开发者应找到性能优化的突破口,持续关注进行改造。
(三)、性能优化
针对影响性能的因素进行优化。
针对影响性能的因素,进行优化的概要:
- 优化延迟
- 使用 CDN
- 缓存(cache)
- 优化带宽的使用
- 懒加载
- 预加载
- 不加载——缓存
- DNS 耗时优化
- 限制域名数量
- DNS 预加载
- TCP 耗时优化
- 减少页面重定向
- 使用 CDN
- 优化资源文件的大小
- 压缩资源
- 摇树——去除库中没用的部分
- 移除昂贵的库
1、优化延迟
(1)、使用 CDN
(2)、使用缓存(cache)
缓存是最快的比任何服务器、CDN 都快。缓存的目的是尽可能减少客户端和服务端的交互。
缓存的实现:
- 通过 Cache-Control HTTP 标头合理制定缓存;
- 使用 hash tag。
cookie 缓存
session 缓存
localstorage 缓存
PWA 缓存。
2、优化带宽的使用
(1)、延迟加载资源:懒加载。下面是懒加载的实现方式
- 最传统的方式——出现在可视区则加载。
- Chrome 75 之后的浏览器提供了一种“原生的懒加载方式”——在需要使用的懒加载的资源上加一个
loadding="lazy"
属性即可。由于这不是 W3C 标准的一部分,所以其他浏览器的兼容性很差。
(2)、提前加载资源:预加载。
预加载的用途:常用于加载首屏的重要资源:document(html)、关键 CSS 和 JavaScript。
预加载的实现:
- 先通过 Performance 和 Lighthouse 识别阻塞 FCP 的关键资源;
- 然后用 link 标签加载该关键资源,在 link 标签里使用
rel="preload"
即可——preload 引导浏览器更早的加载关键资源。
preload 与 prefetch 的区别:
- preload 可以理解为重要紧急的资源,必须立即加载。
- prefetch 可以理解为重要但不紧急的资源,有空就加载,别等着。
【拓展】prefetch 的使用场景:
在 spa 应用里,有一个比较大的资源,该资源是用户去点击当前页面的一个按钮进入到下一个页面的时候才用到的数据,并且满足大多数用户都会点击这个按钮,此时就可以在上一个页面空闲期间去尝试下载这个资源。这个时候建议采用 prefetch 来加载该资源。只有满足大概率被使用的资源才是必要的资源,非必要不使用 prefetch,过多的使用会造成带宽的浪费。
(3)、不加载资源
使用缓存(cache)。上面说过了。
3、DNS 耗时优化
若在 Chrome Devtools 里看到警告提示请求的 DNS 时间过长时,就需要考虑 DNS 优化了。
- 限制 domain(域名)数量;
- 不要使用 Domain Sharding——绕过“浏览器最多只能允许一次同时建立 6 个 TCP 链接的限制”。
- DNS Prefetch:与 prefetch 类似,用来告诉浏览器——如果你闲的时候,就把我发给你的域名解析一下。通过 link 标签来实现
<link rel='dns-prefetch' href='//xxx' />
。弊端是,有可能用户就不会点击触发这个域名的内容模块,这就会造成一定概率的资源浪费。需平衡着适时而用。该功能是实验性功能,兼容性不好。
4、TCP 耗时优化
- 减少页面重定向。因为根据 W3C 的标准,当页面重定向时所有的 TCP 链接都会重新建立,这是非常昂贵的。
- 使用 CDN 减少 TCP 的开销。因为 TCP 的大部分开销是来自于网络延迟的。通过 CDN 降低网络延迟,就能减少 TCP 的开销。
5、优化资源文件的大小:
(1)、数据压缩优化
- Gzip(gzip) 压缩算法:Gzip 是一个通用的压缩算法,不仅可以用来压缩浏览器的网站资源,还可以用来压缩任何其他资源。
- Broti(br) 压缩算法:Broti 是一个 Google 专为 HTTP 传输的资源提供的压缩算法。已经写在了 W3C 标准里,浏览器兼容性良好。
- Broti 需要服务端做相应的配置
- 如果是静态资源网站,需要做 Nginx 的配置。
- Broti 需要更多的压缩成本,对移动端的性能是个考验。
- HTTP 2.0 头文件压缩:随着web功能越来越复杂,请求数量越来越多,于是更多的带宽被占用浪费掉。为了改善这种状况,HTTP 2.0 用 HAPCK 方式对头文件进行了压缩。
- Minification 压缩:牺牲代码的可读性,采取比较暴力的手段尽可能的减小文件体积。可以通过调用成熟的库来实现。
(2)、Tree Shaking(摇树)
Tree Shaking 的实现是基于 ES6 采用了 import 语法而不是 CommonJS 的 require 语法引入的模块。
把引入的库里没有用到的部分排除掉。找到库里没有用到的部分去除,确保在输出的文件中不包含这些无用的内容。
(3)、移除昂贵的 Library(库)
如果你使用的是 webpack 打包工具,那么你可以使用 webpack-bundle-analyzer 来查看当前项目中打包后库的体积大小情况。对于一些体积较大的库,可以寻找平替方案,来移除昂贵的库。
6、网站优化的终极方式
升级 HTTP。关于 HTTP 请戳这里。
四、从文件类型的角度看静态资源优化
静态资源包括:html、javascript、css、图片等。
1、图片
图片的优化:
- 真的需要图片吗?
- Iconfront 代替图片
- 使用 Data URL 代替图片(base64)
- 采用 Image spriting(雪碧图)
- 逐步加载图片:使用统一占位符,在图片没有加载出来之前给用户展示统一占位符图像。
- 可以提供默认的图像占位符,或者使用插件:lqip(低质量图像占位符)和 sqip(基于 SVG 的图像占位符)。
- 图片的压缩:
- 用 jdf-png-native 压缩 png
- 用 jpegtran 压缩 jpg / jpeg
- 用 gifsicle 压缩 gif
- 响应式图片:根据用户不同的窗口大小以及不同的设配像素情况下,来展示不同大小的图片。
- JS 绑定事件监测窗口大小来实现。
- CSS 媒体查询来实现。(推荐)
- 使用 HTML5 的 img 标签的 srcset 属性来实现:
<img srcset='img-320w.jpg, img-640w.jpg 2x, img-960w.jpg 3x' src='img-960w.jpg' alt='' />
其中“2x”、“3x”表示图像的设备像素比。src 属性指定的是默认展示的图片。
- 在后端进行图片的优化:
- 在图片的url地址上,设置图片的尺寸、格式、压缩比等,传入后端后,后端根据参数进行对应的处理,处理后返回给前端即可。
2、html、javascript、css
- 用前端自动化构建工具(比如:webpack)对 html、javascript、css 分别做打包、压缩处理。
- 减少回流和重绘。
(1)、html 的优化
- 减少不必要的 html 结构嵌套。
- 使用相对路径的 url。
- 在 html 文件中,不同类型的文件应该放在合适的位置:
- CSS 文件尽量放在 html 头部。否则页面最后才渲染 CSS 的话,可能会造成页面白屏。
- JS 文件进量放在文件底部。防止 JS 加载阻塞页面的渲染。
- 增强用户体验:
- 增加首屏必要的 JS 和 CSS,从而加快首屏的渲染。
- 设置
favicon.ico
图标。否则控制台会报错,而且浏览器窗口上没有 logo 信息。
(2)、css 优化
- 减少样式层级的嵌套。
- 删除不必要的 0,比如:
0.4rem
可以省略写成.4rem
。 - 合理使用 Web Fonts。
- 动画要稍微延迟一点。
(3)、javascript 优化
- 尽量减少对 DOM 节点的操作。
- 动画尽量采用 CSS 实现,少用 JS 来实现。
- 合理的使用缓存。
- ES6 import 模块化。
- 动态路由。
【扩展阅读】