前言
前端性能优化对于一个前端来讲好比是“演员的自我修养”,想做一名好的前端,一定要懂如何性能优化。另外性能优化基本上是面试必问的一个问题了,这一块能回答的很漂亮,无疑是一个加分项。
感知性能
对于用户来说,用户的感知性能才是最重要的,简单讲,就是让用户感觉你的网站访问很快,并且感知性能没有衡量标准。
如果一个页面的加载时间很长,我们也可以通过一些方式让用户觉得没有那么慢。
loading
最基础的菊花等待~
骨架屏
可以参考antd-design的骨架屏
客观性能
对于开发者来说,性能指标是可以客观度量的,我们可以通过一些手段来优化 Web 性能,使这些度量指标达到开发者设定的标准。
客观性能是指,从用户输入url开始,到下载、解析和执行所有资源以及最终绘制的整个过程的时间度量。
浏览器打开网页的过程
1.浏览器对URL进行DNS解析
2.浏览器与服务器进行TCP连接
3.浏览器发出HTTP请求
4.服务器返回HTTP响应
5.浏览器进行页面渲染
性能指标
Google提出的网站用户体验的三大核心指标
LCP、FID、CLS
LCP 代表了页面的速度指标
FID 代表了页面的交互体验指标
CLS 代表了页面的稳定指标
常用的性能优化方法
减少请求次数
资源合并
使用打包工具,对js、css资源进行打包,避免文件过多
使用雪碧图
图片走cdn等
缓存
HTTP Cache
强缓存
Expires http1.0的产物,现在已经不用
Cache-Control
针对浏览器和服务器时间不同步,加入了新的缓存方案;这次服务器不是直接告诉浏览器过期时间,而是告诉一个相对时间Cache-Control=10秒,意思是10秒内,直接使用浏览器缓存
app.get('/demo.js',(req, res)=>{
let jsPath = path.resolve(__dirname,'./static/js/demo.js');
let cont = fs.readFileSync(jsPath);
res.setHeader('Cache-Control', 'public,max-age=120') //2分钟
res.end(cont)
})
强缓存的特点是不需要询问服务器
协商缓存
强制缓存的弊端很明显,即每次都是根据时间来判断缓存是否过期;但是当到达过期时间后,如果文件没有改动,再次去获取文件就有点浪费服务器的资源了
协商缓存是请求服务器后,服务器来判断是返回新的资源,还是告诉浏览器使用旧的资源
有根据最后修改时间、和文件内容是否改动两种协商方法
Last-Modified和If-Modified-Since
只能精确到秒,如果一秒内多次修改,就感知不到
确实修改后,但内容没变,也会新请求
ETag和If-None-Match
解决文件修改时间不精确带来的问题,只有当文件内容改变时,ETag才改变
服务器读取磁盘文件demo.js,返给浏览器,同时带上文件上次修改时间 Last-Modified(GMT标准格式)
app.get('/demo.js',(req, res)=>{
let jsPath = path.resolve(__dirname,'./static/js/demo.js')
let cont = fs.readFileSync(jsPath);
let status = fs.statSync(jsPath)
let lastModified = status.mtime.toUTCString()
if(lastModified === req.headers['if-modified-since']){
res.writeHead(304, 'Not Modified')
res.end()
} else {
res.setHeader('Cache-Control', 'public,max-age=5')
res.setHeader('Last-Modified', lastModified)
res.writeHead(200, 'OK')
res.end(cont)
}
})
服务器读取磁盘文件demo.js,返给浏览器,同时带上文件的唯一标识ETag
Memory Cache & Disk Cache
他们是配合http缓存的。 memory cache命中最快,但是它周期较短,base64的图片,较小的js和css能够有较大几率被写进内存,这没有确定的定论。 其他较大的js、css和图片等会被直接写进硬盘,进行缓存。
存储
cookie
最大4K,存储一些用户登录状态
webStorage
分为sessionStorage和localStorage,大小在5-10M,键值对存储,区别是生命周期不同,sessionStorage在tab关闭后,就不存在了,localStorage永久存在,除非主动删除。
减少请求体积
资源压缩
Gzip
传输的时候可以在服务器端开启gzip压缩,可以有效减少传输文件的大小,可以在响应头content-encoding: gzip中看到。
代码压缩
使用一些代码压缩工具,删除掉无用的注释、空行和缩减名称等操作来减少文件体积。
图片压缩
图片是网页上占用很多流量的一种资源。如果在图片损失一些颜色和像素的情况下并不会对用户体验有太大影响,那么就应该对图片进行压缩。
图片压缩
PNG无损格式,压缩率一般,支持透明背景,常用于透明图片或者Icon等。
JPG有损格式,压缩率较好,常用于复杂的大图,不支持透明背景。
SVG矢量图形,可编程。在各分辨率下不失真,但是渲染复杂图形较消耗性能。常用于简单图形。
WEBP无损格式,相较于PNG和JPG来说,压缩率更好,同时支持透明背景。唯一的缺点是兼容性不好。可用于兼容性好的浏览器,用JPG和PNG做回退机制。
服务器发送HTTP相应
减少响应时间
利用CDN(高流量大并发情况下)
cdn全称content delivery network。它是依靠部署在各地区的边缘服务器,达到用户就近获取内容,降低网络拥塞,提高用户访问速度和命中率的目的。它主要的关键技术是内容存储和分发技术。
降低页面初始渲染时间
预渲染
将浏览器解析 javascript 动态渲染页面的这部分工作,在打包阶段就完成了,(只构建了静态数据)换个说法在构建过程中,webpack 通过使用 prerender-spa-plugin 插件生成静态结构的 html。
服务器渲染(SSR)
CSR 项目的 TTFP(Time To First Page)时间比较长,参考之前的图例,在 CSR 的页面渲染流程中,首先要加载 HTML 文件,之后要下载页面所需的 JavaScript 文件,然后 JavaScript 文件渲染生成页面。在这个渲染过程中至少涉及到两个 HTTP 请求周期
node做为中间层,让 React 代码在服务器端先执行一次,使得用户下载的 HTML 已经包含了所有的页面展示内容,这样,页面展示的过程只需要经历一个 HTTP 请求周期
同时,由于 HTML 中已经包含了网页的所有内容,所以网页的 SEO 效果也会变的非常好。之后,我们让 React 代码在客户端再次执行,为 HTML 网页中的内容添加数据及事件的绑定,页面就具备了 React 的各种交互能力。
页面渲染
减少阻塞
js阻塞
当html解析遇到js会先下载和执行js文件,这是为了防止js操作了dom等情况的发生。但我们作为操作者,可以人为的指定,那些元素可以延迟加载。
为script标签指定 async 或 defer来延迟脚本。
async表示js不会阻塞,并行执行,但会在下载完成后立刻执行,谁先加载好谁执行
defer则会在下载完成并且整个文档解析完成、DOMContentLoaded事件被触发前开始执行,按照顺序执行
css阻塞
css会阻塞html进行渲染,但是为了界面没有任何样式的展现在用户面前,因此我们需要将css提前
减少渲染次数
避免回流和重绘
回流又称为重排,即通过某种手段改变了元素的位置大小等信息,导致浏览器需要重新计算和渲染的过程。而重绘只是被改变了样式如背景和颜色等。
不论是哪一种,都会耗费性能,所以我们要避免进行循环操作。
减少渲染节点数量
懒加载
对于一些不在用户视图内的元素,我们可以在展示的时候先不进行渲染,直到该元素出现在了视图内再进行渲染。
懒加载包括对图片或者dom元素的加载和渲染
提高渲染效率
减少DOM节点的操作
浏览器的渲染引擎和js引擎是分离的,可以想象在js引擎和渲染引擎之间进行”跨界交流“并不简单,这个开销很大,所有我们要尽量避免这种操作。
降低选择器的复杂性
.box:nth-last-child(-n+1) .title { /* styles */ }
浏览器计算此结果可能需要大量的时间,但我们可以把选择器的预期行为更改为一个类:
.final-box-title { /* styles */ }
避免强制同步布局和布局抖动
浏览器每次布局计算时几乎总是会作用到整个DOM,如果有大量元素,将会需要很长时间才能计算出所有元素尺寸和位置。
所以我们应该避免在运行时动态的修改集合属性(宽高)。如果无法避免,优先使用Flexbox
内联首屏关键css(Critical CSS)
浏览器在将我们的页面呈现给用户之前一定要先完成页面引用到的CSS文件的下载和解析(download and parse),所以link标签链接的CSS资源是渲染阻塞的(render-blocking)。如果CSS文件非常大或者网络的状况很差,渲染阻塞的CSS会严重影响用户体验。针对这个问题,社区有一种优化方案就是将一些重要的CSS代码(Critical CSS)直接放在头部的style标签内,其余的CSS代码再进行异步加载,这样浏览器在解析完HTML后就可以直接渲染页面了。
/* critical CSS */ …body goes here
可以直接npm I criticalcss
那么如何定义Critical CSS呢?放在head标签内的CSS当然是越少越好,因为太多的内容会加大html的体积,所以我们一般把用户需要在首屏看到的(above the fold)页面要用到的最少CSS提取为Critical CSS。由于页面在不同的设备上展示的效果不同,对应着的Critical CSS内容也会有所差别,因此Critical CSS的提取是一个十分复杂的过程,虽然社区有很多对应的工具可是效果都差强人意。CSS-in-JS却可以很好地支持Critical CSS的生成。在CSS-in-JS中,由于CSS是和组件绑定在一起的,只有当组件挂载到页面上的时候,它们的CSS样式才会被插入到页面的style标签内,所以很容易就可以知道哪些CSS样式需要在首屏渲染的时候发送给客户端,再配合打包工具的Code Splitting功能,可以将加载到页面的代码最小化,从而达到Critical CSS的效果。
换句话来说,CSS-in-JS通过增加一点加载的JS体积就可以避免另外发一次请求来获取其它的CSS文件。而且一些CSS-in-JS的实现(例如styled-components)对Critical CSS是自动支持的。
作者:Delicious96345
链接:https://juejin.cn/post/6959091982731837447
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。