web前端项目性能优化

web前端项目性能优化

本篇文章主要回答两个问题:

  1. 什么是回流(reflow)、重绘(repaint)?它们将怎样影响前端性能?
  2. 一些前端页面卡顿的原因以及解决方法

1. 什么是回流(reflow)、重绘(repaint)?

参考的文章:https://www.html.cn/archives/4996

https://blog.csdn.net/claireke/article/details/51375622

è¿éåå¾çæè¿°

首先了解浏览器页面渲染顺序:

1. HTML文档和 CSS文件同时开始渲染、HTML文档生成DOM树、CSS文件生成CSSOM对象

2. CSSOM对象和DOM树做连接匹配生成render树

3. render树在页面上进行布局,这个过程叫reflow

4. render树在页面上进行绘制,这个过程叫repaint

概念解释:

reflow: 当render树中的一部分或者全部因为大小边距等问题发生改变而需要重建的过程叫做回流 ( 几何大小和位置发生改变 )
repaint: 当元素的一部分属性发生变化,如外观背景色不会引起布局变化而需要重新渲染的过程叫做重绘 ( 文字、边框、背景颜色、外观风格 )

注意:回流必定触发重绘,而重绘不一定触发回流

引起回流的原因:

    1.首当其冲自然是dom树结构变化,比如你删除或者添加某个node.
    2.元素几何属性变化,包括margin,padding,height,width,border等
    3.浏览器窗口发生变化 resize 事件发生时
    4.激活伪类 hover
    5.计算offsetWidth和offsetHeight属性值的时候

现在我们大概都能得出的结论是:回流比重绘的代价要高(因为回流需要页面的节点进行重新构建排列,并且还要再次进行重绘),至于具体的花销跟render树有多少节点需要重新构建有关。

2. 如何减少回流(reflow)、重绘(repaint)?

1.使用display:none技术,只引发两次回流和重绘。

原理:由于display属性为none的元素不在渲染树中,对隐藏的元素操 作不会引发其他元素的重排。如果要对一个元素进行复杂的操作时,可以先隐藏它,操作完成后再显示。这样只在隐藏和显示时触发2次重排。

2.使用DocumentFragment进行缓存操作,引发一次回流和重绘。

原理:我们经常使用javascript来操作DOM元素,比如使用appendChild()方法。每次调用该方法时,浏览器都会重新渲染页面。如果大量的更新DOM节点,则会非常消耗性能,影响用户体验

  javascript提供了一个文档片段DocumentFragment的机制。如果将文档中的节点添加到文档片段中,就会从文档树中移除该节点。把所有要构造的节点都放在文档片段中执行,这样可以不影响文档树,也就不会造成页面渲染。当节点都构造完成后,再将文档片段对象添加到页面中,这时所有的节点都会一次性渲染出来,这样就能减少浏览器负担,提高页面渲染速度

 3.不要经常访问会引起浏览器flush队列的属性,如果你确实要访问,利用缓存。利用变量把节点的offsetTop等属性值缓存起来使用。

原理:如果每句JS操作都去回流重绘的话,浏览器可能就会受不了。所以很多浏览器都会优化这些操作,浏览器会维护1个队列,把所有会引起回流、重绘的操作放入这个队列,等队列中的操作到了一定的数量或者到了一定的时间间隔,浏览器就会flush队列,进行一个批处理。这样就会让多次的回流、重绘变成一次回流重绘。

虽然有了浏览器的优化,但有时候我们写的一些代码可能会强制浏览器提前flush队列,这样浏览器的优化可能就起不到作用了。当你请求向浏览器请求一些 style信息的时候,就会让浏览器flush队列,比如:

  1. offsetTop, offsetLeft, offsetWidth, offsetHeight
  2. scrollTop/Left/Width/Height
  3. clientTop/Left/Width/Height
  4. width,height
  5. 请求了getComputedStyle(), 或者 IE的 currentStyle

当你请求上面的一些属性的时候,浏览器为了给你最精确的值,需要flush队列,因为队列中可能会有影响到这些值的操作。即使你获取元素的布局和样式信息跟最近发生或改变的布局信息无关,浏览器都会强行刷新渲染队列。

3. JS和CSS阻塞导致页面加载缓慢

参考文章:https://www.jb51.net/css/572702.html

CSS不会阻塞DOM树解析,但是会阻塞DOM树渲染(内存中加载好DOM树,但是页面会等CSS解析后和DOM输一起渲染出来)。

CSS加载会阻塞后面的JS语句的执行。

网页中的资源下载一般是并行的(JS除外),CSS也是,但是某些情况下会出现CSS阻塞资源下载的情况。

因为js是单线程的,以及html是按顺序解析的,浏览器加载资源默认按照 html、css、js的顺序,为了避免js对页面进行DOM操作导致回流,所以js会阻塞所有资源的下载,css资源加载若在js之前,css会阻塞js下载,因为css阻塞js,js阻塞所有,变相css阻塞所有。   

可总结如下:    

(1)页面要等待所有的css加载完成后才会开始渲染,css的加载不会阻塞DOM树的加载,但是会阻塞DOM树的渲染,但css本身是可以并行下载的(会迟迟看不到内容,但是DOM树已经解析完成)
(2)css加载会阻塞 js 代码的执行,浏览器会维持 html css js 加载顺序(内嵌,在css标签前的内嵌代码会先执行,在css标签后的代码会等待css文件加载完才执行)
(3)嵌入JS会阻塞所有内容的呈现(DOM树的解析和渲染),而外部JS只会阻塞其后内容的显示。浏览器为了防止出现JS修改DOM树,需要重构DOM树的情况,就会阻止其他资源的下载和呈现。

为了能让其他资源先加载(以最快的速度加载),就要避免js文件加载时的阻塞作用。

解决方法:
(1)把css放头部,把外部js放底部
(2)给script标签添加defer属性,仅限外部脚本,defer属性表示延迟脚本的执行,等到整个文档解析完再执行,defer属性能延迟执行,但是不会延迟下载,浏览器遇到script就立即下载脚本
(3)在页面加载完后动态创建脚本元素
(4)执行时间较长过程的函数可以放入settimeout函数中

 4. 服务器文件加载缓慢可开启GZIP(文件压缩传输)

在服务端配置中开启GZIP压缩,它会把浏览器请求的页面,以及页面中引用的静态资源以压缩包的形式发送到客户端,然后在客户端完成解压和拼装。js文件可以开启GZIP,但是图片文件不要,图片启用GZip压缩,不仅浪费了CPU(压缩需要cpu运算),还增大了体积(压缩后体积不仅没有减少,还增大,可参考其他文章实验分析),势必影响服务器性能,影响网站速度。

 5. 开启CDN 

什么是CDN?

        CDN(Content Delivery Network) 内容分发网络,CDN的本质仍然是一个缓存,而且将数据缓存在里用户最近的地方,使用户以最快的速度获取数据,即网络访问第一跳。
        由于CDN是部署在网络运营商的机房,这些运营商又是终端用户网络的提供商,因此用户请求的第一跳就到达CDN服务器,当CDN服务器中缓存有用户请求的数据时,就可以从CDN直接返回给客户端浏览器,最短路径的返回响应,加快用户的访问速度,减少数据中心的负载压力。

概况:设置多个节点服务器,分布在不同区域中,便于用户进行数据传递和访问。当某一个节点出现问题时,通过其他节点仍然可以完成数据传输工作,可以提高用户访问网站的响应速度。CDN可以通过不同的域名来加载文件,从而使下载文件的并发连接数大大增加。

      CDN能够缓存一般的CSS,js图片等静态资源文件,而且这些文件的访问频率很高,将其缓存在CDN可以极大的提高网站的访问速度。

6. 资源合并和压缩减少HTTP请求次数

多次HTTP请求和连接也很消耗时间。

1、js脚本合并(js文件webpack合并打包)
2、css sprite 精灵图(多个小图合并)
3、图片懒加载(不在页面显示区域的图片先不加载)

7. 合理使用HTTP缓存

合理使用HTTP的缓存机制,让浏览器把数据缓存到本地,再次加载同样的数据的时候,无需再向服务器请求文件,直接可以从本地缓存中读取,减少读取时间。

HTTP请求中合理添加 Expires 或 cache-control 报文头

8. 使用SPDY/HTTP2协议

SPDY,一种开放的网络传输协议,由Google开发,用来发送网页内容。基于传输控制协议(TCP)的应用层协议 ,是 HTTP/2 的前身。SPDY 的作用就是,在不增加域名的情况下,解除最大连接数的限制。主要的特点就是多路复用,他的目的就是致力于取消并发连接上限,减少HTTP连接的次数。

9. 域名发散和域名收敛

9.1 域名发散

        浏览器的并行连接数(同域名),在一些现代浏览器内每个 hostname 的最大连接数基本都是6个。所以 PC 时代对静态资源优化时,通常将静态资源分布在几个不同域,保证资源最完美地分域名存储,以提供最大并行度,让客户端加载静态资源更为迅速。

        域名发散就是为了突破浏览器对于同一域名并发请求数的限制,使用域名发散为同一个服务申请多个域名,从而可以一定程度上提高并发量;当然,由于建立新的请求需要一定的代价,因此需要在域名发散与域名收敛间进行trade off,通常发散的域名个数为2-4个。

9.2 域名收敛

        域名收敛就是将静态资源放在一个域名下不进行发散,这主要是为了适应移动端的发展需求;通常DNS是一个开销较大的操作,而移动端由于网络带宽和实时性、资源等的限制,这些开销对移动端的用户体验是致命的,因此需要进行域名收敛。域名收敛,减少域名数量可以降低 DNS 解析的成本。

你可能感兴趣的:(web前端项目性能优化)