1.想深层次探究前端优化,就不仅仅停留到表面,要知道文件如何下载?浏览器如何渲染?
想优化页面,就要知道一个页面,产生的整个过程
浏览器产生一个页面的流程如下:
1.1下载:最有优化点的一部
下载,这个方向是优化必要点,贴一张chrome下载文件的时间截图:
//来个表格,细致分析下下载过程中每个阶段的耗时
1.持续时间 = 时间 *个数
2.时间 = initial(浏览器初始化,准备数据)+request(请求到服务端) + serverProcess (服务处理数据)+response(返回数据,接受第一个字符到接受完最后一个字符的时间)
3. initial = queue(处理队列任务) + DNS解析 + tcp协议 +ssl协议
4. dns = delay*2(小体量信息,耗时大概是两个网络延时)
5. tcp = delay*3 ( 三次握手)
6. ssl = delay*5 (是一种文件安全协议,需要5次握手)
7. request = delay + size/brandwidth(带宽)
8. response = delay + size/brandwidth(带宽)
分析出:下载性能决定的几个因素:
- 增加带宽
- 减少资源排队
- 减少网络延迟
- 减少网络延迟影响
- 控制资源总大小
下面逐个分析下:
1.1.1.增加带宽:
这个在用户端我们是无法控制的,难道公司出钱给大家都上一个百兆光纤?还有一个情况你会发现,在20M和 50M的带宽下,打开一个页面或者文件速度差不多,这就是因为带宽达到一个级别后,影响下载的速度的主要优化点就是其他方向了。
所以增加带宽,我们就在自己公司的网络上搞高点,用户端无能为力了。
1.1.2.减少资源排队:
-
浏览器有“单域名”最大并行请求数量的限制
ie7 - 2个
ie8 - 6个
ie10 - 8个
chrome - 6个
什么360浏览器好像比较变态,并行十几个。
建议使用时,并行下载数折中取 4个,来处理。 -
分域名追求更高的并行度
把什么资源放到不同的域名下面,通常将静态资源分布在几个不同域,保证资源最完美地分域名存储,以提供最大并行度,让客户端加载静态资源更为迅速。 -
分域名追求更高的并行度,与域名收敛取舍
分域名在PC上是比较可行的,因为网络环境较好,但是在移动端,多个分域名,就会造成多个DNS解析,造成很大的时间开销浪费在DNS解析上。
首先要知道,使用一个http请求去请求一个资源时,会经历些什么。简单而言:
1、DNS 域名解析 -->
2、发起 TCP 的 3 次握手 -->
3、建立 TCP 连接后发起 http 请求 -->
4、服务器响应 http 请求
5、......略
在这里第一步,也是关键的第一步 DNS 解析,在移动端的 http 请求耗时中,DNS 解析占据了大部分时间。
注:所以说,资源分域名和域名收敛要自行折中
- 过多并行请求会造成Keep-Alive无效,进而增加网络延迟影响
Keep-Alive是什么?
在http早期,每个http请求都要求打开一个tpcsocket连接,并且使用一次之后就断开这个tcp连接。
使用keep-alive可以改善这种状态,即在一次TCP连接中可以持续发送多份数据而不会断开连接。通过使用keep-alive机制,可以减少tcp连接建立次数,以此提高性能和提高http服务器的吞吐率(更少的tcp连接意味着更少的耗时。
1.1.3.减少网络延迟及影响:
-
减少DNS查询(与分域名冲突,要找最优点)
-
减少DNS等待
1.空请求DNS Prefetch,如下面代码
1. 用meta信息来告知浏览器, 当前页面要做DNS预解析:<meta http-equiv="x-dns-prefetch-control" content="on" />
2. 在页面header中使用link标签来强制对DNS预解析: <link rel="dns-prefetch" href="http://bdimg.share.baidu.com" />
<meta http-equiv="x-dns-prefetch-control" content="on" />
<link rel="dns-prefetch" href="http://nsclick.baidu.com" />
<link rel="dns-prefetch" href="http://hm.baidu.com" />
<link rel="dns-prefetch" href="http://eiv.baidu.com" />
-
减少TCP链接
1.Keep-Alive应用
2.合并资源(就是打包咯。。) -
让服务器距离用户更近
- CDN加速走起。。。
1.1.4.控制资源提炼:
-
资源体量
1.压缩、去注释 -
Cookie 大小
- 1.分情况用Storage代替Cookie
- 2.无Cookie需求独立CookieFree域名(一些静态资源,放到Cookie Free域名下,设置不传Cookie)
1.1.5.合并资源:
原理:
- 减少了tcp握手消耗
- 要最大并行请求数量限制(并不是所有的东西都打包到一个文件里最好,要充分利用浏览器并行多个文件下载的特性)
?合并资源存在的问题:
- 脚本/样式解析被延迟(文件过大,解析当然延迟了)
- 存在带入无用内容的概率(多个文件打包在一起,可能存在此时还不需要的代码)
- 版本更新被绑在一起(有一个小的文件改动,build时就要更改版本,缓存被破坏的概率更高)
因此要从几个方便寻找合并文件的平衡点:
//一下建议有一部分是书面所得,并未实践
1.文件大小(建议单个打包文件不超过 500KB)
2.更新频率(有的文件更新频率过高,就尽量不打包进来,造成破坏整个文件缓存)
3.特别大的库单独拥有自己的一个脚本,比如ECharts之类的图表。
4.UI控件库,包含基础UI控件和业务UI控件,合并为一个脚本。
5.MVC框架、页面基类、工具类、系统通用功能层等业务无关的逻辑合并为一个脚本。
6.一级页面的业务模块合并为一个脚本。
1.1.6.缓存:
Cache缓存:(通过设置HTTP Header 参数Cache-Control实现)
1. 静态资源版本号+永久缓存是基本
2. 动态/入口资源短时间缓存有积极作用(比如商品的详情页缓存个几秒还是应该可以的)
Last-Modified & If-Modified-Since(也就是咱们常见的304)
localStorage 缓存数据
HTML 的localStorage是是本地存储的新玩意,适当的时候可以代替cookie,但是注意兼容问题和体积上限(大致5M左右)
注:上面是大致对‘下载这一过程的总结’
1.2解析:
解析页面:
1.解析是需要时间的,虽然很短
2.解析发生在主线程,会阻塞其它代码执行
3.解析和下载发生在不同线程,可并行HTML可使用http协议设置 Transfer-Encoding:chunk实现下载和解析并行
上图中,上面的是大部分网站的形式,下面这种,边下载边解析是比较少见的,上下对比起来,还是有一定的优化控件。
目前下面的这种形式Facebook有实践,大家可以搜索一下“BigPipe技术”,貌似淘宝也有一种“BigRender”的技术。(具体实现不详述了,感兴趣可以搜索下)
1.3样式匹配:
- 基本无法成为瓶颈
- 样式匹配自右向左进行
- 注意避免统配选择器(*)
注:个人感觉一般样式选择器对性能影响不大,好的书写是有利于代码的维护。
1.4布局方面的优化(有优化点):
1.4.1. 浏览器repaint(重绘) & relayout(排版)
不同的浏览器的渲染过程存在些许不同,但大体的机制是一样的,下图展示的是浏览器自下载完全部的代码后的大致流程
1.HTML代码转化成DOM
2.CSS代码转化成CSSOM(CSS Object Model)
3.结合DOM和CSSOM,生成一棵渲染树(包含每个节点的视觉信息)
4.生成布局(layout),即将所有渲染树的所有节点进行平面合成
5.将布局绘制(paint)在屏幕上
//这五步里面,第一步到第三步都非常快,耗时的是第四步和第五步
注意: 重绘和重排的性能消耗是非常严重的,破坏用户体验造成UI卡顿。
?什么时候回触发重新渲染?
1.增加、删除、更新DOM节点;
2.通过display:none隐藏节点会触发重绘和回流,通过visibility:hidden隐藏只会触发重绘,因为没有几何结构的改变;
3.移动节点和动画;
4.增加、调整样式;
5.用户操作行为,如调整窗口大小、改变字体大小、滚动窗口(OMG,no!)等。
//重新渲染,就需要重新生成布局和重新绘制。前者叫做"重排"(reflow),后者叫做"重绘"(repaint)。
//注意点:"重绘"不一定需要"重排",比如改变某个网页元素的颜色,就只会触发"重绘",不会触发"重排",因为布局没有改变。但是,"重排"必然导致"重绘",比如改变一个网页元素的位置,就会同时触发"重排"和"重绘",因为布局改变了。
现在浏览器变得很聪明了,会智能合并多次重绘动作,如:
div.style.width = 12px;
div.style.color = #000;
这两行代码,只会触发一次重排和重绘
但是有时候特别书写时就不行,如下:
div.style.color = 'blue';
var margin = parseInt(div.style.marginTop);
div.style.marginTop = (margin + 10) + 'px';
//上面代码对div元素设置背景色以后,第二行要求浏览器给出该元素的位置,所以浏览器不得不立即重排。
所以,从性能角度考虑,尽量不要把读操作和写操作,放在一个语句里面。
// bad
div.style.left = div.offsetLeft + 10 + "px";
div.style.top = div.offsetTop + 10 + "px";
// good
var left = div.offsetLeft;
var top = div.offsetTop;
div.style.left = left + 10 + "px";
div.style.top = top + 10 + "px";
一般的规则:
1.样式表越简单,重排和重绘就越快。
2.重排和重绘的DOM元素层级越高,成本就越高。
总结重绘和重排的几个优化点:(尽量少的触发重排)
1.DOM 的多个读操作(或多个写操作),应该放在一起。不要两个读操作之间,加入一个写操作。
2.如果某个样式是通过重排得到的,那么最好缓存结果。避免下一次用到的时候,浏览器又要重排。
3.不要一条条地改变样式,而要通过改变class,或者csstext属性,一次性地改变样式。
4.先将元素设为display:none(需要1次重排和重绘),然后对这个节点进行100次操作,最后再恢复显示(需要1次重排和重绘)。这样一来,你就用两次重新渲染,取代了可能高达100次的重新渲染。
5.position属性为absolute或fixed的元素,重排的开销会比较小,因为不用考虑它对其他元素的影响。
6.使用虚拟DOM的脚本库,比如React等。
7.dom动画什么的,不要通过修改DOM位置进行,可以用transform等css属性,只触发重绘
一个页面的性能体验可以总结一下几点:
- 对性能进行分类
- 首屏时间
- 绘制帧数
- 交互响应时间
-
时间的大致标准
-
用户交互反馈100ms以上有延迟感(点个按钮100ms没反应)
- 绘制16ms以上有掉帧感 (网上查的,对动画不是太了解)
- 任务执行1s以上有不耐烦感(一个loading超过1s)
- 当一个页面动画很卡时,干脆砍了,用其他方案代替