前端性能优化策略
注意: 最好是在修改之后进行前后对比,在使用了优化方法之后和没使用的时候,因为就算使用了优化方式,也不一定会起到性能优化的效果,需要据场景而定。
http
请求过程当中潜在的性能优化点
- 静态资源一般放到 cdn 加快静态资源的获得,但是当我们 cdn 的域名和页面的域名相同的时候,请求会带上不许不需要的
cookies
,所以两者的域名不能一样。 - dns 缓存, 降低访问 dns 的时间。
- 对于接口,我们可以减少
http
请求的次数和大小。 - 浏览器缓存。
- 页面渲染过程优化。
- 使用服务端渲染。
资源压缩与合并
在真实的开发中都是使用构建工具(webpack、gulp等)来完成下面的事情
html
压缩
去掉空格、注释、回车、换行。
- 使用在线网网站进行压缩(实际公司中不适用)。
- 使用 nodejs 提供的
html-minifier
。 - 后端模板引擎渲染压缩。
css
压缩
去掉空格、注释、回车、换行无效代码删除语义合并。
- 使用在线网网站进行压缩(实际公司中不适用)。
- 使用 nodejs 提供的
html-minifier
。 - 使用clean-cs对css进行压缩。
js
压缩与混乱
无效字符的删除、剔除注释、代码语义的缩减和优化(如变量名缩短)、代码保护(将js
代码混乱,使其不可读)。
- 使用在线网站进行压缩。
- 使用html-minifier。
- 使用uglifyjs2对
js
进行压缩 。
文件合并
文件合并可以减少网络请求, 而且浏览器能够同时并发的请求数量有限,当请求过多的时候可能需要等待,文件合并可以减少请求次数,减少等待。但是存在首屏渲染问题(第一次请求时间长)、缓存失效(单个文件的缓存失效会导致合并当中其他文件的缓存失效)的问题。所以有如下的合并建议。
- 公共库合并: 公共库一般不会变,我们可以将它们进行合并。
- 不同页面的合并: 针对单页应用,只有当我们跳转到那个页面的时候,才去请求这个页面的资源,将这个页面涉及的资源进行合并。
那么怎么进行文件合并呢?如下:
- 使用在线网站进行文件合并。
- 使用nodejs实现文件合并。
图片优化
每种图片格式都有自己的特点,针对不同的业务场景选择不同的图片格式很重要
jgp
有损压缩png
: 颜色类型丰富的图片,应该选择位数高的png
png8
: 256色,支持透明png24
: 2^24色 不支持透明png32
: 2^24色 支持透明
不同格式图片常用的业务场景
- jpg有损压缩,压缩率高,不支持透明: 大部分不需要透明图片的业务场景
- png支持透明,浏览器兼容好: 大部分需要透明图片的业务场景
- webp压缩程度更好,在ios webview有兼容性问题: 安卓全部
- svg矢量图,代码内嵌,相对较小,图片样式相对简单的场景: 图片样式相对简单的业务场景
图片压缩
- 可使用tinypng这个网站来压缩
css
雪碧图
把网站上用到的一些图片整合到一张单独的图片当中,从而减少大量的 http
请求的数量。缺点是图片会变大
- 第一步是将图片合并到一张图片上
- 第二步是使用在线网站,上传图片,选中指定的图片,然后会有相应的
css
。
Image inline
将图片内嵌到html
当中, 减少网站的 http
请求数量, 如使用 base6
的方式插入图片, 一般网站当中一些小的图标可以用它,实际根具情况而定
使用矢量图
使用 svg
进行矢量图的绘制,图片的质量和速度都特别好,使用得也比较多。
寄予页面渲染过程的优化
并发
浏览器对于一个域名的并发请求数量是有限的,所以我们需要设置多个域名,比如 cdn
设置多个域名
懒加载和预加载
懒加载
- 一般都是对于图片来说,当图片进入可视区域的时候再加载图片
- 将
url
地址放置到img
标签当中,当进入可视区的时候,将其取出来设置
预加载
- 图片等静态资源在使用之前的提前请求
- 资源使用到的时候从缓存当中加载
有三种方式:
- 直接在页面上把
img
标签放到前面, 然后display: none;
var image = new Image(); image.src = xxxx;
使其下载下来,后面使用的时候后会直接去缓存拿- 使用
XMLHttpRequset
,url
为image.src
的url
, 可以使用它更加精细的对预加载的过程进行控制 - 使用
preload.js
库
客户端存储
Cookie
用来维护用户状态
Cookie
的生成方式: 服务端的response.header
使用set-cookie
, 或者使用document.cookie
进行读写- 浏览器端在相应的域名下保存
cdn
的静态资源不需要Cookie
, 但是在相同域名下面,http
请求会自动带上Cookie
, 解决办法是cdn
的域名和主站的域名要不相同。
localstorage
对于不会经常变得数据,可以存在里面
- 在
html5
当中设计出来专门用于浏览器存储的 - 大小为
5M
左右 - 浏览器缓存方案
sessionStorage
- 会话级别的浏览器存储(一个
tab
) - 大小为5M左右
- 如使用它进行表单信息的维护(提升用户体验)
IndexedDB
- 用于客户端存储大量结构化数据
- 为应用创建离线版本
Service Workers
可以启动一个其他的线程来进行运算, 防止阻塞。
- 使用拦截和处理网络请求的能力去实现离线应用
- 使用那个
Service worker
在后台运行同时能和页面通信的能力。 - 存储对应于浏览器当中的cache
PWA
是一个 web app
新模型
浏览器自动缓存
需要response-header
和request-header
进行配合,每个文件在进行缓存存储的时候,都会将response-header
一起存,方便下一次判断缓存情况时使用response-header
当中和缓存相关的头字段。而且如果是直接从缓存当中读取。
命中max-age、expire
缓存策略的状态码为200
, 命中etag、last-modified、s-maxage
缓存策略的时候状态码为304
。
响应头当中的和缓存相关的头字段以及状态码,都需要我们在服务端判断是否为响应的情况后,手动设置
Cache-Control
可存在于requset-header
和response-header
max-age
: 再这个时间之内,再次请求相同资源的时候,浏览器会直接从缓存中取,不会发起请求,状态码为200
s-maxage
: 优先级高于max-age
, 用来设置public
的缓存。状态码为304
, 注意这里是去公共的缓存区域取数据,如cdn
, 不是在浏览器本地区数据。private
: 在浏览器上的缓存。对应max-age
public
: 如cnd
, 大家都可以访问。对应s-maxage
no-cache
: 设置了这个属性,不会像max-age
一样,如果没有过期,那么直接去缓存取数据,而是会先发起一次请求,这个时候会带上If-None-Match
或者if-Modified-since
去服务端询问缓存是否过期,如果没有过期服务端返回304
, 然后去缓存区取数据,否者返回200
, 并返回新的内容。no-store
: 当设置了这个属性,就不会使用缓存策略
Expires
缓存过期时间,比max-age
的优先级低
Last-Modified/If-Modified-Since
前面两个头部,可以解决在指定的时间内在缓存当中去取数据,但是当我们的服务端将数据更改之后,客户端并不知道。
服务端返回一个文件最后修改时间Last-Modified
, 在请求的时候带上if-Modified-since
,在服务端进行比对,如果客户端传过来的时间小于服务器上文件的最后修改时间,那么就返回200
,并将相应的最新内容和Last-Modified
给返回。否者返回304
,文件内容未被修改。
需要注意的是,当设置了max-age
的时候,仍然走缓存,只有当max-age
过期了之后,这两个字段才会起作用
Etag / If-None-Match
前面的 Last-Modified/If-Modified-Since
,存在一个缺点,那就是当服务端Last-Modified
修改之后,可能文件内容并没有发生改变,这个时候,其实并没有必要去重新返回内容。所以我们可以使用文件的hash
值,只有文件的hash
值发生改变之后,文件内容才发生改变。
同样需要注意的是,只有在max-age
或者Expire
失效之后,Etag / If-None-Match
才有效。到服务端进行比对,未改变返回304
,改变了返回200
Etag / If-None-Match
的优先级比Last-Modified / If-Modified-Since
优先级高。
服务端性能优化
用服务端的运算能力,来减轻浏览器端的运算压力。
我们以React
为例来说: 当我们访问以为用React
写的网站时,首先会下载我们的代码,但是由于React是基于数据驱动的,所以它还要进行一步渲染的过程,会将我们的JSX
转换成virtualdom
, 然后再转换成html
代码,这一过程是需要时间的。
解决方案在服务端将React
代码渲染成html
之后再返回浏览器端
- 构建层模板编译
- 数据无关的
prerender
方式 - 服务端渲染