一、提高网页加载速度的必要性
国际知名的一组来自Jupiter Research的数据显示:购物者在访问网站过程中的不满会导致销售损失和品牌受损,其中 77%的人将不再访问网站 ,62%的人不再从该网站上购买,48%会转向竞争对手,28%的人对公司产生负面印象。
此组数据分析显示:Google网站访问速度每慢400 ms就将导致用户搜索请求下降 0.59%;Amazon表示,增加 100ms的网站延迟将导致其收入下降 1%;雅虎网站如果有400ms 延迟会导致流量下降 5-9%…
大数据分析表明:当网站首页打开时间超过4秒时,约60%的用户会放弃继续访问浏览,83%的用户希望首页打开时间不要超过4秒。
据调查发现,影响用户体验的因素有很多,如购物方便性、网站合理布局等,但最重要的是网站性能和网站可用性能,这两项对用户的影响占到了69%。
结语:网站网页加载速度越慢,用户体验度就越低,将导致网站拥有者的收益大幅减少。因此,提高网页加载速度就成了前端工程师的必修课之一。
二、前端性能优化
网页加载顺序:1.DNS查找 > 2.下载并渲染HTML文件 > 3.下载并执行css及js组件 > 4.下载图片
以下性能优化列表,按照对网页加载速度的提升幅度排序
1. 减少DNS查找
DNS查找,即浏览器根据url中域名,查找该域名对应的服务器IP地址,然后才能根据服务器IP地址,下载到文件。在DNS查找完成之前,所有的文件下载都无法执行。每一次DNS查找时间约20-120ms。
一般而言,电脑会进行DNS缓存,包括浏览器缓存、系统缓存、路由器缓存、ISP DNS缓存。所以,浏览器DNS查找顺序一般是这样的:浏览器缓存→系统缓存→路由器缓存→ISP DNS 缓存→递归搜索。
递归搜索,即ISP的DNS服务器从根域名开始进行递归查询,查找时间一般为20-120ms。
若没有DNS缓存,才会执行DNS递归搜索。但是显而易见,第一次访问网站首页时,是不会有DNS缓存的,必然会执行DNS查找。而每一个DNS查找,需要耗时20-120ms。因此,减少DNS查找能加快网页加载速度。
实例:
在开发页面时,很多时候,我们都要用到别人已经封装好了的js及css文件,譬如jquery库、angularjs库、时间选择插件、定位插件等等诸多插件,一般而言,网上都有公用的CDN,我们不需要将这些文件下载到本地,就可以使用它们。
譬如:w3c上推荐了谷歌的CDN,腾讯地图也有自己的CDN库,我们可以通过以下代码,获取到谷歌的jquery库,以及腾讯的前端定位插件。
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.8.0/jquery.min.js"></script><!-- 引用谷歌jQuery库 -->
<script type="text/javascript" src="https://3gimg.qq.com/lightmap/components/geolocation/geolocation.min.js"></script><!-- 引用腾讯前端定位插件 -->
这样写做起来十分方便,我们可以省去下载文件这一步骤。但实际上,这样做会增加两个DNS查找,即"ajax.googleapis.com"和"3gimg.qq.com"的DNS查找,将会在首次加载网站首页时,拖慢首页加载速度40-240ms,而且每再增加一个DNS查找,还会额外拖慢20-120ms的加载时间,这是极为致命的。
不仅如此,这两个文件的下载速度分别取决于谷歌服务器及腾讯服务器的下载速度,这是完全不可控的。而将自己网站所需要的文件,寄放在别人的服务器上,也是不安全的。例如,某一天谷歌的服务器抽风,无法再下载文件,或是谷歌不再支持该jquery库,那么我们的网站将会失去jquery核心库,会直接崩溃。更可怕的是,若谷歌在该jquery库文件中添加某些攻击性手段,将会直接导致网站处于危险状态中。
因此,为了减少DNS查找,为了保障网站的安全,我们必须将网站所需的文件下载到本地,而不是调用别人支持的CDN。
2. CSS优先加载,JS延迟加载
在解析HTML文件,构建DOM树时,一旦遇到link标记时,即遇到了CSS样式表,将之下载,便可立即构建渲染树,从而立即呈现页面效果。
而一旦遇到script 标记时,即遇到了JS脚本,将立即阻塞DOM树的构建,将控制权移交给 JavaScript 引擎,等到 JavaScript 引擎运行完毕,浏览器才会从中断的地方恢复渲染树的构建。这涉及到浏览器渲染原理,详情请见本人上一篇博客:浏览器渲染页面的原理及流程
若将引入JS脚本的链接放到HTML页面顶部,那么在加载该页面时,一旦遇到JS,页面渲染就会停滞,出现一段时间的灰色空白,直到JS加载完成,才会出现页面内容,这对用户体验是不友好的。因此,我们需要将JS脚本放置到页面底部,或者让JS脚本异步或是延迟加载。
实例:如下图所示,该首页有CSS样式表一个,若将CSS样式表置于JS脚本之下,那么在加载完JS脚本之前,页面都不会进行渲染,会出现108ms的灰色空白。该首页有JS脚本文件5个,若将这5个JS文件放置在该首页的顶部,那么该首页加载JS脚本时,也将会出现108ms的灰色空白。
从严格意义上来说,CSS的优先加载及JS的延迟加载并不能从根本上提升网页加载速度,但是它们能使网页更快被渲染出来,使页面内容逐步呈现,增加用户等待的耐心,提升用户体验。
3. 减少HTTP请求
HTTP请求,即客户端到服务器端的请求消息,包括资源请求、数据处理等。
HTTP请求需从客户端发起请求,然后由服务器端进行数据处理,然后再返回数据或资源。一般而言,耗时据请求资源的大小,服务器网速,约数ms-数百ms之间。请求资源越大,所花费的时间越长,服务器网速越慢,所花费的时间也越长。
一般而言,完成了DNS查找后,接下来便是进行HTTP请求,获取资源。首先下载HTML文件,然后解析HTML文件,根据HTML内容,获取CSS、JS及图片文件。每一个CSS链接、JS链接以及图片链接都是一个HTTP请求。
每一个HTTP请求都需要花费额外的时间。因此,我们可以将一些可合并的资源进行合并,譬如将所有页面的css合并成一个style.css文件,譬如将所有页面的js合并成一个function.js文件,再譬如将一批小图标利用ps合成一张图片(此手段效果最显著,也最常用)。虽然有时文件会变大,但是在HTTP请求中,请求下载一个大小为100KB的资源文件,比请求下载两个大小为50KB的资源文件要快。
从实际测试来看,每减少一个HTTP请求,据请求资源大小,能加快网页加载速度约数ms-数百ms。
实例:合并图标
"用户信息页面"如下图所示:四个输入框左侧,有四张icon,合计大小8.9KB,四个HTTP请求合计时间23ms。
我利用ps,将此四张图片进行合并,然后以css的背景图片进行引用,其结果如下图所示:四个输入框左侧,四张icon合并后的大小6.0KB,一个HTTP请求时间8ms。减小图片大小2.9KB,提升加载速度15ms。看起来提升速度不多,但按比例算,提升幅度巨大。
4. 缩小文件
众所周知,HTTP请求中,返回的资源越大,HTTP请求所花费的时间越长,因此,缩小资源文件可以提升HTTP请求的速度,进而提升页面加载速度。不仅如此,还能节省服务器流量及空间。
一般而言,缩小文件主要是指图片压缩,也包括CSS、JS文件压缩,网上有成熟的代码在线压缩工具,譬如:在线JS/CSS/HTML 压缩
而图片压缩主要是指图片在不同的格式下、不同的分辨率下保存,其大小将会有巨大的差异。譬如同一张图片的png格式与jpg格式肉眼看起来几乎没有区别,但是其大小相差了约5倍。而jpg格式中级也比高级要小约莫1倍。当然,图片的不同格式有不同的用处,且分辨率越高,图片也越清晰。
因此,根据需求为图片选择合适的格式及分辨率,就能得到最小的图片文件。
具体操作:在ps中打开图片,同时按下ctrl+alt+shift+s,打开"存储为web所用格式"弹框,即可任意选择保存图片的格式及分辨率(若按操作无法顺利压缩图片,请自行度娘)。
实例:同一张图片,jpg格式比png格式小约5倍,加载速度快约3倍。
缩小文件来提升页面加载速度,从我个人的项目优化经验来看,其效果极为显著,多个项目都提升了超过100ms的加载时间。
5. 善于利用缓存
避免在HTML文件中使用style标签插入CSS样式,及使用script标签插入JS脚本。若在HTML文件中插入CSS及JS,那么它们无法进入缓存,每次刷新页面,都要重新加载,不但浪费了浏览器资源,拖慢了页面加载速度,而且显得冗余且复用性低,不利于日后的维护。因此,将CSS样式与JS脚本分离出来,形成CSS文件及JS文件,就能进入缓存,进而提高页面加载速度。
灵活使用cookie和localstorage。在使用接口时,灵活使用cookie和localstorage来缓存接口返回的信息,避免不必要的接口查询,从而提升页面加载速度。譬如:在登录页面登录时,缓存好用户信息,设置过期时间。在进入用户个人中心页面时,若数据并未过期,可以直接从缓存中取用户信息,不必再调起接口去获取用户信息。
实例:css、js、图片都可进行缓存,从缓存中获取文件,时间为0ms。
总结:以上几点,对于页面加载速度的提升效果都很显著,只要做到以上几点,只要服务器不坑,项目页面加载速度都不会很慢。以下我将会从代码的角度来提出几点项目优化的经验。
6. HTML文件代码优化
1. 避免使用空请求,包括空的href链接、空src链接。空链接本身无法请求成功,因此会把一个HTTP请求拖到超时,而且空链接会阻塞页面中其他资源的下载进程,会拖慢页面加载速度。譬如:<img src="" alt="">。
2. 根据项目大小,选择主要使用class还是id。id选择器优先级最高,访问速度最快。但是在html中每声明一个id,就会在JS底层声明一个全局变量,而全局变量的增多,将会拖慢JS中变量遍历的效率,若变量遍历达到十万次以上,就会出现较显著的延迟,而且容易造成全局变量污染。对于小项目,并无影响,但是对中大型项目来说,尤其是游戏项目,影响很大。个人推荐,当项目较小时,灵活使用class和id,当项目较大时,尽量少使用id。
3. 预先设定图片大小。在页面加载过程中,图片最后加载,若不对图片预设大小,当图片加载完成后,将会引起大量的重排,将会浪费浏览器资源及拖慢页面加载速度。
4. 尽量减少DOM元素的数量与层级。解析HTML时,标签的数量越多,标签的层级越深,浏览器解析构建DOM树的时间就越长,应尽可能的减少DOM元素的数量和层级。
5. 尽量避免使用table标签。浏览器对table标签的解析是全部生成后再一次性绘制的,因此会造成表格位置较长时间的空白,推荐使用ul及li标签绘制表格。
6. 使用异步加载iframe标签。浏览器加载iframe标签时,会阻塞父页面渲染树的构建及HTTP请求,因此尽量使用异步加载iframe。
等等…
7. CSS样式代码优化
1. 禁止使用样式表达式。CSS表达式从IE5起开始支持,但仅有IE支持。它的解析速度较慢,而且运算次数远比我们想象的要大,随意动动鼠标就能轻松达到上万次运算,会对页面性能造成影响。譬如:"#myDiv{width:expression(document.body.offsetWidth - 110 + "px"); }"
2. 优化关键选择器,去掉无效的父级选择器,尽量少在选择器末尾使用通配符。大多数人都认为,浏览器对CSS选择器的解析式从左往右进行的,譬如选择器:"#myDiv ul li a",大多数人会认为这个选择器效率极高,毕竟第一个ID #myDiv 就已经把范围限定了,先选择 #myDiv ,再在 #myDiv 下寻找 ul ,再一级一级往下,直到找到 a 标签,效率很高。事实上这是错的,浏览器对CSS选择器的解析式从右往左进行的。在上述选择器中,浏览器会先去寻找 a 标签,范围为全局,再在 a 标签的列表中,寻找父级标签是 li 标签的 a 标签,一直向上,直到最后,找到父级标签是 #myDiv ul li 的a标签。因此,效率并不像想象中那么高。显而易见,"#myDi a"选择器比"#myDiv ul li a"选择器效率要高得多。而通配符 a 的效率远比类选择器及id选择器低,若给 a 标签添加一个class myA ,构造新选择器:"#myDiv .myA",它的效率又远比"#myDi a"要高了。浏览器对CSS选择器的解析式从右往左进行,因此在选择器末尾最好使用类选择器,而不是通配符。CSS选择器效率问题详情请见:CSS选择器效率问题
等等…
8. JS代码优化
1. ajax请求方法按需求选择get或是post,访问接口所花费的时间在页面加载时间中占很大的比重,而接口访问方法中,get方法远比post方法要快,因此按需选择接口访问方法很重要。
2. 减少全局变量,尽量使用局部变量。js中,全局变量运算速率远低于局部变量,速度差异达到上百倍,且全局变量越多,全局变量的查找速率便越慢。详情请见:减少全局变量对效率的提升
3. 减少对DOM的操作。js操作DOM将会引起页面的重绘及重排,需要花费时间及耗费浏览器资源。
等等…
结语:前端性能优化是一门完整的学问,并非一两篇博客所能详尽的。以上我只是按照页面加载速度的提升幅度写了几点,其实还有大量优化手段,难以详尽。譬如:图片懒加载、按需加载、预加载等等,再譬如:从网络加载角度进行优化、从渲染角度进行优化、从架构协议角度进行优化等等
学无止境,诸君共勉!