前言
当我们去面试的时候,很大概率会被面试官问这么一个问题:你有尝试过对项目做性能优化吗?或者你了解哪些性能优化的方法?听到这个问题的你可能是这样的:
似曾相识但又说不清楚,往往只能零散地说出那么几点,难以做到有条理的回答。那么,本文就带你简单了解前端性能优化的几个主要方面,旨在抛砖引玉。
一、资源的合并和压缩
请求过程中一些潜在的性能优化点:
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
压缩与混乱(丑化)
- 使用在线网站进行压缩
- 使用
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.图片压缩
针对真实图片情况,舍弃一些相对无关紧要的色彩信息,对图片进行压缩;
2.css
雪碧图
将网站上用到的一些图片整合到一张单独的图片中,从而减少网站HTTP
请求数量。原理为:设定整张雪碧图可示区域,将想要显示的图标定位到该处(左上角);**缺点:**整合图片比较大时,一次加载比较慢。
如天猫的雪碧图:
很多情况下,并不是所有的小图标都放在一张雪碧图中,而是会适当进行拆分。现在使用雪碧图的场景比较少了。
自动生成雪碧图样式
3.网页内联图片(Image inline
)
将图片的内容内嵌到html
当中,减少网站的HTTP
请求数量,常用于处理小图标和背景图片。网页内联图片写法为:
<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAA..." alt="">
缺点:
- 浏览器不会缓存内联图片资源;
- 兼容性较差,只支持
ie8
以上浏览器; - 超过
1000kb
的图片,base64
编码会使图片大小增大,导致网页整体下载速度减慢;
所以要根据场景使用,不过内联图片减少HTTP
请求的优点还是很显著的。比如,在开发中小于4KB
或8KB
的图片都会通过构建工具自动inline
到HTML
中,这种情况下Image inline
带来的图片大小增长其实是比增加HTTP
请求次数更优的。
4.矢量图SVG
与iconfont
使用iconfont
解决icon
问题
应尽量使用该方式,比如可以采用阿里巴巴矢量图库:
可以选择格式进行下载:
可以看到它们的大小有着明显的差异:
使用SVG
进行矢量图的控制
SVG
意为可缩放矢量图形(Scalable Vector Graphics
)。SVG
使用 XML
格式定义图像。
5.webp
webp
的优势体现在它具有更优的图像压缩算法,能带来更小的图片体积,而且拥有肉眼识别无差异的图像质量;同时具备了无损和有损的压缩模式、Alpha
透明以及动画的特性。在JPEG
和PNG
上的转化效果都非常优秀、稳定和统一。安卓上不存在兼容性问题,推荐安卓下使用。
以下为淘宝网首页请求的图片:
可以看到,图片中大量地添加了webp
格式的选择。.jpg_.webp
表示当浏览器支持webp
时采用webp
格式,否则采用jpg
格式。
下面为B
站首页的图片,可以看到基本都采用了webp
格式:
同一张图片jpg
格式和webp
格式压缩率有着明显的差异:
可以通过在线网站将图片转换为webp
像图片这样的静态文件可以存放在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
来更新网页。
补充:
HTML
中可能会引入很多的css、js
这样的外部资源,这些外部资源在浏览器端是并发加载的。但是浏览器会对同一域名进行并发数量(度)的限制,即单个域名的并发度是有限的;- 所以,经常将大部分的资源托管到
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
。