前端性能优化清单,超详细,一篇文章足矣,建议看完(1)
前端性能优化清单,超详细,一篇文章足矣,建议看完(1)
当我们去面试的时候,很大概率会被面试官问这么一个问题:你有尝试过对项目做性能优化吗?或者你了解哪些性能优化的方法?听到这个问题的你可能是这样的:
似曾相识但又说不清楚,往往只能零散地说出那么几点,难以做到有条理的回答。那么,本文就带你简单了解前端性能优化的几个主要方面,旨在抛砖引玉。
一、资源的合并和压缩
web前端应用的开发与部署过程:
输入url到页面显示出来的过程:
请求过程中一些潜在的性能优化点:
- dns是否可以通过缓存减少dns查询时间?
- 网络请求的过程如何走最近的网络环境?
- 相同的静态资源是否可以缓存?
- 能否减少http请求的大小和次数?
- 能否进行服务端渲染?
总结:深入理解http请求的过程是前端性能优化的核心。
优化核心
- 减少http请求数量;
- 减少请求资源的大小;
google首页案例学习
- html压缩;
- css压缩;
- js的压缩和混乱;
- 文件合并;
- 开启gzip;
1.html压缩
HTML代码压缩就是压缩一些在文本文件中有意义,但是在HTML中不显示的字符,包括空格,制表符,换行符等,还有一些其他意义的字符,如HTML注释也可以被压缩;
一个简单的计算:
google的流量,占到整个互联网的40%,预计2016年全球网络流量将达到1.3ZB(1ZB = 10^9TB),那么google在2016年的流量就是1.3ZB * 40%,如果google每1MB请求减少一个字节,每年可以节省流量近500TB流量。
如何进行html压缩
- 使用在线网站进行压缩;
- nodejs提供的html-minifier工具;
- 后端模板引擎渲染压缩;
2.css代码压缩
分为两部分:
- 无效代码的压缩;
- css语义合并;
如何进行css压缩
- 使用在线网站进行压缩;
- 使用html-minifier对html中的css进行压缩;
- 使用clean-css对css进行压缩;
3.js压缩与混乱(丑化)
包括:
- 无效字符的删除(空格,回车等);
- 剔除注释;
- 代码语义的缩减和优化;
- 代码保护(如果代码不经处理,客户端可直接窥探代码漏洞);
JS压缩与混乱(丑化)
- 使用在线网站进行压缩:https://tool.oschina.net/jscompress/
- 使用html-minifier对html中的js进行压缩;
- 使用uglify.js2对js进行压缩;
4.文件合并
文件合并的好处:
左边的表示使用http长链接keep-alive但不合并请求的情况,需要分三次去获取a.js、b.js、c.js;右边是使用长链接并且合并请求的情况,只需要发送一次获取合并文件a-b-c.js的请求,就能将三个文件都请求回来。
不合并请求有下列缺点:
- 文件与文件之间有插入的上行请求,会增加N-1个网络延迟;
- 受丢包问题的影响更严重:因为每次请求都可能出现丢包的情况,减少请求能有效减少丢包情况;
- keep-alive本身也存在问题:经过代理服务器时可能会被断开;
文件合并存在的问题
- 首屏渲染问题:当请求js文件的时候,如果页面渲染只依赖a.js文件,由于文件合并,需要等待合并后的a-b-c.js文件请求回来才能继续渲染,这样就会导致页面渲染速度变慢。这种情况大多出现在现代化的前端框架,如Vue等的使用过程中;
- 缓存失效问题:合并后的文件a-b-c.js中只要其中一个文件(比如a.js)发生变化,那么整个合并文件都将失效,而不采用文件合并就不会出现这种情况;
使用建议
- 公共库合并:将不经常发生变化的公共组件库文件进行合并;
- 将不同页面的js文件单独合并:比如在单页面应用SPA中,当路由跳转到具体的页面时才请求该页面需要的js文件;
如何进行文件合并
- 使用在线网站进行文件合并;
- 使用nodejs实现文件合并;
- 使用webpack等前端构件化工具也可以很好地实现;
二、图片相关的优化
有损压缩过程:
一张JPG图片的解析分别要进行:
- 颜色空间的转换:从RGB的颜色空间转到其他的颜色空间 ;
- 进行重采样:区分高频和低频的颜色变换;
- 进行DCT过程:对高频的颜色采样结果进行压缩,这样压缩的收益会比较大;
- 再对数据进行量化;
- 最后进行编码(encoding);
最终得到JPEG-Compressed Image Data,即真正显示出来的JPG图片。虽然这是一种有损压缩,但是很多情况下,这些损失的数据并不影响显示;
png8/png24/png32之间的区别
- png8:256色 支持透明;
- png24:2^24色 不支持透明;
- png32:2^32色 支持透明;
不同格式图片常用的业务场景
- jpg有损压缩,压缩率高,支持透明;应用:大部分不需要透明图片的业务场景;
- png支持透明,浏览器兼容好;应用:大部分需要透明图片的业务场景;
- webp(2010年由谷歌推出)压缩程度更好,在ios webview中有兼容性问题;应用:安卓全部;
- svg矢量图,代码内嵌,相对较小,用于图片样式相对简单的场景;应用:比如logo和iconfont;
1.图片压缩
针对真实图片情况,舍弃一些相对无关紧要的色彩信息,对图片进行压缩;比如在线压缩网站:https://tinypng.com/
2.css雪碧图
将网站上用到的一些图片整合到一张单独的图片中,从而减少网站HTTP请求数量。原理为:设定整张雪碧图可示区域,将想要显示的图标定位到该处(左上角);缺点:整合图片比较大时,一次加载比较慢。
如天猫的雪碧图:
很多情况下,并不是所有的小图标都放在一张雪碧图中,而是会适当进行拆分。现在使用雪碧图的场景比较少了。
自动生成雪碧图样式的网站:http://www.spritecow.com/
选中雪碧图中对应的图标,就会生成对应的样式。
3.网页内联图片(Image inline)
将图片的内容内嵌到html当中,减少网站的HTTP请求数量,常用于处理小图标和背景图片。网页内联图片写法为:
<imgsrc='data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAA...'alt=''>
浏览器上的表现形式为:
这里提供一个将:image 转 DataUrI的网址:http://tool.c7sky.com/datauri/
缺点:
- 浏览器不会缓存内联图片资源;
- 兼容性较差,只支持ie8以上浏览器;
- 超过1000kb的图片,base64编码会使图片大小增大,导致网页整体下载速度减慢;
所以要根据场景使用,不过内联图片减少HTTP请求的优点还是很显著的。比如,在开发中小于4KB或8KB的图片都会通过构建工具自动inline到HTML中,这种情况下Image inline带来的图片大小增长其实是比增加HTTP请求次数更优的。
4.矢量图SVG与iconfont
使用iconfont解决icon问题
应尽量使用该方式,比如可以采用阿里巴巴矢量图库:
可以选择格式进行下载:
可以看到它们的大小有着明显的差异:
使用SVG进行矢量图的控制
SVG 意为可缩放矢量图形(Scalable Vector Graphics)。SVG 使用 XML 格式定义图像。下为示例:
在线转换网站:http://www.bejson.com/convert/image_to_svg/
5.webp
webp的优势体现在它具有更优的图像压缩算法,能带来更小的图片体积,而且拥有肉眼识别无差异的图像质量;同时具备了无损和有损的压缩模式、Alpha透明以及动画的特性。在JPEG和PNG上的转化效果都非常优秀、稳定和统一。安卓上不存在兼容性问题,推荐安卓下使用。
以下为淘宝网首页请求的图片:
可以看到,图片中大量地添加了webp格式的选择。.jpg_.webp表示当浏览器支持webp时采用webp格式,否则采用jpg格式。
下面为B站首页的图片,可以看到基本都采用了webp格式:
同一张图片jpg格式和webp格式压缩率有着明显的差异:
可以通过在线网站将图片转换为webp:https://zhitu.isux.us/
像图片这样的静态文件可以存放在CDN服务器上,让CDN服务器批量地将图片转换成Webp格式;
三、浏览器渲染引擎与阻塞
1.渲染的主要模块
版本一:
版本二:
一个渲染引擎主要包括:HTML解析器,CSS解析器,javascript引擎,布局layout模块,绘图模块:
- HTML解析器:解释HTML文档的解析器,主要作用是将HTML文本解释成DOM树;
- CSS解析器:它的作用是为DOM中的各个元素对象计算出样式信息,为布局提供基础设施;
- Javascript引擎:使用Javascript代码可以修改网页的内容,也能修改css的信息,javascript引擎能够解释javascript代码,并通过DOM接口和CSS树接口来修改网页内容和样式信息,从而改变渲染的结果;
- 布局(layout):在DOM创建之后,Webkit需要将其中的元素对象同样式信息结合起来,计算他们的大小位置等布局信息,形成一个能表达这所有信息的内部表示模型;
- 绘图模块(paint):使用图形库将布局计算后的各个网页的节点绘制成图像结果;
2.渲染过程
浏览器渲染页面的整个过程:浏览器会从上到下解析文档。
- 浏览器解析时遇见 HTML 标记,就会调用HTML解析器解析为对应的 token (一个token就是一个标签文本的序列化)并构建 DOM 树(就是一块内存,保存着tokens,建立它们之间的关系)。在生成DOM的最开始阶段(应该是Bytes → characters后),并行发起css、图片、js的请求,无论他们是否在HEAD标签中。注意:发起js文件的下载请求(request)并不需要DOM处理到那个script节点;
- 遇见 style/link 标记 调用解析器 处理 CSS 标记并构建 CSS样式树;
- 遇见 script 标记 调用 javascript解析器处理script标记,绑定事件、修改DOM树/CSS树等;
- 将 DOM树 与 CSS树 合并成一棵渲染树(Render Tree)。
- 布局(Layout):根据渲染树中各节点的样式和依赖关系,计算出每个节点在屏幕中的位置;
- 绘图(Painting):按照计算出来的结果:要显示的节点、节点的CSS与位置信息,通过显卡,把内容画到屏幕上;
经过第一次Painting之后DOM、CSSOM、Render Tree都可能会被多次更新,比如JS修改了DOM或者CSS属性时,Layout和Painting就会被重复执行。除了DOM、CSSOM更新的原因外,图片下载完成后也需要调用Layout 和 Painting来更新网页。
补充:
1. HTML中可能会引入很多的css、js这样的外部资源,这些外部资源在浏览器端是并发加载的。但是浏览器会对同一域名进行并发数量(度)的限制,即单个域名的并发度是有限的;
2. 所以,经常将大部分的资源托管到CDN服务器上,并且设置3~4个CDN域名。防止只有一个CDN域名的情况下,达到了浏览器外部资源并发请求数目的上限,导致很多资源无法做到并发请求。所以,应设置多个CDN域名;
3.css阻塞
只有通过link引入的外部css才会产生阻塞:
- style标签中的样式:
- 由html解析器进行解析;
- 不阻塞浏览器渲染(可能会产生“闪屏现象”);
- 不阻塞DOM解析;
- link引入的外部css样式(推荐使用的方式):
- 由CSS解析器进行解析;
- 阻塞浏览器渲染:由于css已经加载完毕,所以整个渲染过程是带样式的,所以这种阻塞可以避免“闪屏现象”;
- 阻塞其后面的js语句的执行:这个不难理解,js文件中经常会出现DOM操作,操作过程中有可能涉及到css样式的修改。实际上,这些修改往往是依赖于之前引入的css设定的样式的,所以css会阻塞js的执行;
- 不阻塞DOM的解析;
- 优化核心理念:尽可能快的提高外部css加载速度:
- 使用CDN节点进行外部资源加速;
- 对css进行压缩(利用打包工具,比如webpack,gulp等);
- 减少http请求数,将多个css文件合并;
- 优化样式表的代码;
4.js阻塞
- 阻塞DOM解析:原因:浏览器不知道后续脚本的内容,如果先去解析了下面的DOM,而随后的js删除了后面所有的DOM,那么浏览器就做了无用功,浏览器无法预估脚本里面具体做了什么操作,例如像document.write这种操作,索性全部停住,等脚本执行完了,浏览器再继续向下解析DOM;可以通过给script标签添加defer和async属性,异步引入js文件,以此来解决这一问题。
- 阻塞页面渲染:原因:js中也可以给DOM设置样式,浏览器同样等该脚本执行完毕,再继续干活,避免做无用功;
- 阻塞后续js的执行:原因:js是按顺序执行的,这样可以维护依赖关系,例如:必须先引入jQuery再引入bootstrap;
- 不阻塞资源的加载:这并不与上面矛盾,因为不可能由于加载一个js文件就把其他资源的加载都阻塞了。针对这种常见的情况,浏览器会通过预加载的方式加载后续的资源;
5.总结
- css的解析和js的执行是互斥的(互相排斥),css解析的时候js停止执行,js执行的时候css停止解析;
- 无论css阻塞,还是js阻塞,都不会阻塞浏览器加载外部资源(图片、视频、样式、脚本等);因为览器始终处于一种:“先把请求发出去”的工作模式,只要是涉及到网络请求的内容,无论是:图片、样式、脚本,都会先发送请求去获取资源,至于资源到本地之后什么时候用,由浏览器自己协调。显然这种做法效率很高;
- WebKit 和Firefox 都进行了【预解析】这项优化。在执行js脚本时,浏览器的其他线程会解析文档的其余部分,找出并加载需要通过网络加载的其他资源。通过这种方式,资源可以在并行连接上加载,从而提高总体速度。请注意,预解析器不会修改 DOM 树
四、懒加载和预加载
1.懒加载
图片进入可视区域之后再请求图片资源的方式称为图片懒加载。适用于图片很多,页面很长的业务场景,比如电商;
懒加载的作用:
- 减少无效资源的加载:比如一个网站有十页图片,用户只查看了第一页的图片,这就没必要将十页图片全都加载出来;
- 并发加载的资源过多会阻塞js的加载,影响网站正常的使用:由于浏览器对某一个host name是有并发度上限的,如果图片资源所在的CDN和静态资源所在的CDN是同一个的话,过多图片的并发加载就会阻塞后续js文件的并发加载。
懒加载实现的原理:
监听onscroll事件,判断可视区域位置:
图片的加载是依赖于src路径的,首先可以为所有懒加载的静态资源添加自定义属性字段,用于存储真实的url。比如是图片的话,可以定义data-src属性存储真实的图片地址,src指向loading的图片或占位符。然后当资源进入视口的时候,才将src属性值替换成data-src中存放的真实url。
<img src='' class='image-item' alt='' lazyload = 'true' data-src='TB27YQvbm_I8KJjy0FoXXaFnVXa_!!400677031.jpg_180x180xzq90.jpg_.webp'>
懒加载实例
可以使用元素的getBoundingRect().top来判断当前位置是否在视口内,也可以使用元素距离文档顶部的距离offsetTop和scrollTop是否小于视口高度来判断:
举例
比如手机淘宝首页:
当快要滚动到需要展示的图片时才进行图片的请求,可以看到图片上有一个lazyload的属性:
2.预加载
预加载与懒加载正好是相反的过程:懒加载实际上是延迟加载,将我们所需的静态资源加载时间延后;而预加载是将图片等静态资源在使用之前的提前请求,这样资源在使用到时能从缓存中直接加载,从而提升用户体验;
预加载的作用:
- 提前请求资源,提升加载速度:使用时只需要读取浏览器缓存中提前请求到的资源即可;
- 维护页面的依赖关系:比如WebGL页面,会依赖一些3D模型,这些都是页面渲染所必须的资源。如果资源都没有加载完毕就进行页面的渲染,就会造成非常不好的体验。所以时常使用预加载的方式维护页面渲染的依赖关系,比如将WebGL页面依赖的3D模型加载完之后才进行页面渲染。这样渲染的过程就不会有任何阻碍,具有较好的用户体验;
预加载的实例
例如九宫格抽奖业务,每个奖品都有一个选中态和非选中态,实际上这是由两张图片组合而成的。由于每个奖品的选中过程都是一瞬间,这就对图片的选中态和非选中态切换效率要求很高,如果选中态的图片没有预加载的话显然是来不及的。
所以,实际上对于九宫格中所有图片选中态的样式和对应的图片都需要进行预加载,从而让我们在抽奖的过程中,能够瞬间从缓存中读取到选中态的图片,从而不影响抽奖效果的展示。
除此之外还有网站登录或活动时需要用到的动画,这是在动画需要的每帧图片都完全预加载完之后才会进行显示的。
五、重绘与回流
1.CSS图层
浏览器在渲染一个页面时,会将页面分为很多个图层,图层有大有小,每个图层上有一个或多个节点。在渲染 DOM的时候,浏览器所做的工作实际上是:
1、获取DOM后分割为多个图层;
2、对每个图层的节点计算样式结果(Recalculate style--样式重计算);
3、为每个节点生成图形和位置(Layout--回流和重布局);
4、将每个节点绘制填充到图层位图中(Paint Setup和Paint--重绘);
5、图层作为纹理上传至GUI;
6、复合多个图层到页面上生成最终屏幕图像(Composive Layers--图层重组);
2.创建图层的条件
- 拥有3D或透视变换的css属性(prespective transform );
- 使用加速视频解码的<video>节点;
- 拥有3D(WebGL)上下文或加速的2D上下文的<canvas>节点;
- CSS3动画的插件(如Flash);
- 拥有加速css过滤器的元素;
- transform:如translateZ(0)
- opacity
- filter
- will-change:哪一个属性即将发生变化,进而进行优化。
3.重绘(Repaint)
重绘是一个元素外观的改变所触发的浏览器行为,比如background-color、outline等属性。这些属性不影响布局,只影响元素的外观,风格,会造成DOM元素的重新渲染,这个过程称为重绘。
需要注意的是:重绘是以图层为单位,如果图层中某个元素需要重绘,那么整个图层都需要重绘。比如一个图层包含很多节点,其中有个gif图,gif图的每一帧,都会重回整个图层的其他节点,然后生成最终的图层位图。
因此,可以通过特殊的方式来强制gif图单独为一个图层(translateZ(0)或者translate3d(0,0,0);CSS3的动画也是一样(好在绝大部分情况浏览器自己会为CSS3动画的节点创建图层);
所以:将频繁重绘回流的DOM元素作为一个独立图层,那么这个DOM元素的重绘和回流只会该图层;原则上是要尽量避免新建图层的,因为这会导致图层重组(Composive Layers)时候的计算量增大。所以,只有当某些DOM元素频繁重绘回流时,才新建一个独立图层放置它们;
只会触发重绘的属性
//部分属性colorborder-styleborder-radiusvisibilitytext-decorationbackgroundbackground-imagebackground-positionbackground-repeatbackground-sizeoutline-coloroutlineoutline-styleoutline-widthbox-shadow
4.回流(Reflow)
当render tree中的一部分(或全部)因为元素的规模尺寸,布局,隐藏等改变而需要重新构建。这就称为回流(reflow);
- 当页面布局和几何属性改变时就需要回流;
- 回流必将引起重绘,而重绘不一定会引起回流;
触发页面重布局(回流)的属性
频繁触发重绘和回流,会导致UI频繁渲染。在渲染的过程中由于阻塞了js线程的执行,最终导致js执行变慢。
5.触发回流的常见操作
- 增加、删除、修改 DOM 结点;
- 移动 DOM 的位置;
- 修改 CSS 样式;
- Resize 窗口;移动端没有这个问题,因为移动端的缩放没有影响布局视口(vw/vh);
- 修改网页的默认字体;
- 获取某些DOM元素的属性(width,height等);
注:display:none 会触发 Reflow,而visibility:hidden 只会触发 Repaint,因为没有发生位置变化;
6.示例
案例一:淘宝轮播图
可以使用Chrome浏览器调试工具的Performance来观察淘宝首页一个轮播图引起的重绘回流过程:
Update Layer Tree回流和重布局:
Paint重绘:
Composite Layers图层重组:
案例二:播放器
通过Chrome调试工具的Layers选项查看图层,及新增图层的原因:
视频播放的过程中,video标签的DOM元素会一直重绘,所以把它限制在一个图层上是非常好的,这样只会涉及到这个图层的重绘,而不会影响其他图层的元素。
图层不能滥用,否则会在图层重组的过程中严重消耗性能!
比如可以将淘宝首页的所有的DOM元素都变为一个图层:在html标签中的全局样式(*)中添加transform:translateZ(0)来触发新建图层:
还可以通过添加:will-change: transform属性新建图层;
再次查看此时的图层情况,可以看到此时首页的图层非常之多,十分地卡: