之前写过关于前端性能优化的文章(前端性能优化),但总觉得还是太笼统太敷衍,等到被问到为什么这样就会被绊住脚,今天就详细聊聊关于前端性能优化的点,以及到底为什么是这样。
性能优化主要分为两类:
加载时优化、运行时优化
一、加载时优化
1、减少HTTP请求
一个完整的HTTP请求需要经历DNS查找、TCP握手、、浏览器发送HTTP请求、服务器接收请求、服务器处理请求并且发回响应、浏览器接收响应等过程。
- Queueing:浏览器将资源放入队列的时间
- Stalled:因放入队列时间而发生的停滞时间
- Proxy negotiation:与代理服务器连接进行协商所花费的时间
- Request sent:发出网络请求所花费的时间
- DNS Lookup:DNS解析时间
- Initial connection:建立HTTP连接的时间
- SSL:浏览器与服务器建立安全性连接的时间
- TTFB:等待服务端返回数据的时间
- Content Download:浏览器下载资源的时间
从这张图片看,真正下载数据的时间占比为 0.75/404.79,文件越小,比例越小,文件越大,比例越高。所以建议将多个小文件合并成一个大文件,从而减少HTTP请求次数。
2、使用HTTP2
HTTP2相比于HTTP1.1的优点:
- 解析速度快。服务器解析HTTP1.1的请求时,需要不断读入字节,直到遇到分隔符CRLF为止。而HTTP2是基于帧的协议,每个帧都有表示帧长度的字段。
- 多路复用。HTTP1.1如果同时发起多个请求,就得建立多个TCP连接,因为一个TCP连接同时只能处理一个HTTP1.1的请求。而在HTTP2上,多个请求可以公用一个TCP连接。同一个请求和响应用一个流来表示,并且有唯一的流ID来标识。多个请求和响应在TCP连接中可以乱序发送,到达目的地后再通过流ID重新组建。
- 首部压缩。HTTP2将不同请求中相同的首部存储起来,仅发送它们之间不同的部分,就可以节省不少的溜了,加快请求时间。
- 优先级。HTTP2可以对比较紧急的请求设置一个较高的优先级,服务器在收到这样的请求后可以优先处理。
- 流量控制。由于一个TCP连接流量带宽是固定的,当有多个请求并发时,一个请求占的流量多,另一个请求占的流量就少,流量控制可以对不同流的流量进行精确控制。
- 服务器推送。HTTP2可以对一个客户端请求发送多个响应,也就是说,除了对最初请求的响应外,服务器还可以额外向客户端推送资源,而无需客户端明确地请求。
3、使用服务端渲染
- 客户端渲染:获取HTML文件,根据需要下载JavaScript文件,运行文件,生成DOM,再渲染。
- 服务端渲染:服务端返回HTML文件,客户端只需要解析HTML文件。
区别就是:客户端渲染的网站会直接返回HTML文件,而服务端渲染的网站则会渲染完页面再返回这个HTML文件。
这样客户端渲染的网站需要加载几个文件和HTML文件才能完成页面渲染,而服务端渲染的网站只需要加载一个渲染完成的HTML文件就能完成页面渲染。
4、静态资源使用CDN
CDN(内容分发网络):是一组分布在不同地理位置的Web服务器。当服务器离用户越远时,延迟时间越长,而CDN就是为了解决这一问题,在多个位置部署服务器,让用户离服务器更近,从而缩短请求时间。
5、将CSS放在文件头部,JavaScript放在文件底部
CSS执行会阻塞渲染,组织JS执行,而JS加载和执行会阻塞HTML解析,阻止CSSDOM创建。
若将CSS和JavaScript文件都放在HEAD标签里,这样需要很长时间去加载和解析,这就导致页面空白,为了避免这种情况发生,JS文件就要放在底部(不阻塞DOM解析,但是会阻塞渲染),等HTML解析完了再加载JS文件,尽快向用户呈现页面内容。
关于为什么将CSS文件放在头部:因为先加载HTML再加载CSS,会导致用户第一时间看到的是没有样式的页面,降低用户体验。
其实JS文件也是可以放在头部的,只要给script标签加上defer属性就可以异步下载,延迟执行。
6、善用缓存,不重复加载相同的资源
我们可以通过添加Expires或者max-age来控制浏览器缓存。
Expires设置了一个时间,只要在这个时间之前,浏览器都不会请求文件,而是直接使用缓存。而max-age是一个相对时间,建议使用max-age代替Expires。
7、压缩文件
利用webpack和gzip压缩文件,可以减少文件下载时间。
8、图片优化
(1)使用字体图标代替图片图标
字体图标iconfont将图标制作成一个字体,可以设置属性,例如font-size、color等,优点是不会失真,而且生成的文件比较小。
(2)图片延迟加载
在一个图片量很大的网页中,一次性加载全部图片会对用户体验造成很大影响,可以在页面中先不设置图片路径,只有当图片出现在浏览器的可视区域时,才加载真正的图片,即延迟加载,也叫懒加载。
(3)响应式图片
响应式图片就是能够根据屏幕大小自动加载合适的图片。
通过 picture 实现
<picture>
<source srcset="banner_w1000.jpg" media="(min-width: 801px)">
<source srcset="banner_w800.jpg" media="(max-width: 800px)">
<img src="banner_w800.jpg" alt="">
</picture>
通过 @media 实现
@media (min-width: 769px) {
.bg {
background-image: url(bg1080.jpg);
}
}
@media (max-width: 768px) {
.bg {
background-image: url(bg768.jpg);
}
}
(4)调整图片大小
可以使用缩略图来减少图片加载时间。
例如一些没必要展示整张大图的图片,可以选择缩略图进行占位,当用户点击或者悬浮去看大图时再进行加载。
(5)尽可能利用 CSS3 效果代替图片
有很多图片使用 CSS 效果(渐变、阴影等)就能画出来,这种情况选择 CSS3 效果更好。因为代码大小通常是图片大小的几分之一甚至几十分之一。
二、运行时优化
1、减少重绘重排
重排和重绘这两个操作都是非常昂贵的,因为 JavaScript 引擎线程与 GUI 渲染线程是互斥,它们同时只能一个在工作。
2、使用事件委托
事件委托利用了事件冒泡,只指定一个事件处理程序,就可以管理某一类型的所有事件。所有用到按钮的事件(多数鼠标事件和键盘事件)都适合采用事件委托技术, 使用事件委托可以节省内存。
3、查询语句
当判断条件较多时,越倾向于使用 switch 而不是 if-else。当条件语句特别多时,使用 switch 和 if-else 不是最佳的选择,这时不妨试一下查找表。查找表可以使用数组和对象来构建。
4、不要覆盖原生方法
无论你的 JavaScript 代码如何优化,都比不上原生方法。因为原生方法是用低级语言写的(C/C++),并且被编译成机器码,成为浏览器的一部分。当原生方法可用时,尽量使用它们,特别是数学运算和 DOM 操作。