程序猿进阶路上不能错过之最全前端性能优化秘籍(一)!

周智星
5839     

很多人通常在完成了产品之后才会去考虑性能。把与性能相关的事情拖到项目的最后来做,所做的也不过是对服务器上的config文件进行一些微调、串联、优化以及部分特别小的调整。而现在,技术已经有了翻天覆地的变化。一个项目的性能是非常重要的,除了要在技术层面上注意,更要在项目的设计之初就开始考虑,这样才可以使性能的各种隐形需求完美的整合到项目中,随着项目一起推进。


在性能方面,前端的优化是大家很容易忽视却又十分重要的一点。


前端是庞大的,包括 HTML、 CSS、 Javascript、Image 、Flash等等各种各样的资源。前端优化是复杂的,针对方方面面的资源都有不同的方式。那么,前端优化的目的是什么 ?

▪ 从用户角度而言,优化能够让页面加载得更快、对用户的操作响应得更及时,能够给用户提供更为友好的体验。
▪  从服务商角度而言,优化能够减少页面请求数、或者减小请求所占带宽,能够节省可观的资源。

前端优化的途径有很多,按粒度大致可以分为三类:

第一类是页面级别的优化,例如 HTTP请求数、脚本的无阻塞加载、内联脚本的位置优化等 ;

第二类是服务器端优化,如:添加Expires 或Cache-Control报文头等;

第三类则是代码级别的优化,例如 Javascript中的DOM 操作优化、CSS选择符优化、图片优化以及 HTML结构优化等等。


首先,我把雅虎14条优化原则,《高性能网站建设指南》以及《高性能网站建设进阶指南》中提到的优化点做一次梳理,按照优化方向分类,可以得到这样一张表格:


面级优化

一、减少 HTTP请求数
80%的响应时间花在下载网页内容(images, stylesheets, javascripts, scripts, flash等)。减少请求次数是缩短响应时间的关键!可以通过简化页面设计来减少请求次数,但页面内容较多可以采用以下技巧。
1)捆绑文件 
现在有很多现成的库可以帮你将多个脚本文件捆绑成一个文件,将多个样式表文件捆绑成一个文件,以此来减少文件的下载次数。


2)CSS Sprites
 就是把多个图片拼成一副图片,然后通过CSS来控制在什么地方具体显示这整张图片的什么位置。

咱们来看一个真实的应用案例,如图1:

从图1可以看到这个应用的所有css和js都没有没有合并压缩,我算了一下,总共引入了50个css和js,咱们再开看看图2:

从图2可以看出,一个页面加载最少要4秒,每个css资源http请求就用了两百多毫秒,50个资源就占了一秒多。如果把所有js和css分别合并压缩成一个文件,页面的加载速度就会提高很多。


 现在压缩合并js和css的工具很多,但是考虑到现在好多项目都是maven工程,所以推荐大家使用雅虎的yuicompressor工具进行压缩合并,还可以打出API文档(jresui的API文档就是用这个来打的), yuicompressor除了支持maven方式,也支持后台java代码方式进行合并压缩,具体配置如下:


将外部脚本置底,将CSS放在head中
浏览器是可以并发请求的,这一特点使得其能够更快的加载资源,然而外链脚本在加载时却会阻塞其他资源。如果将脚本放在比较靠前的位置,则会影响整个页面的加载速度从而影响用户体验。解决这一问题的方法有很多,而最简单可依赖的方法就是将脚本尽可能的往后挪,减少对并发下载的影响。


如果将 CSS放在其他地方比如 BODY中,则浏览器有可能还未下载和解析到 CSS就已经开始渲染页面了,这就导致页面由无 CSS状态跳转到 CSS状态,用户体验比较糟糕。除此之外,有些浏览器会在 CSS下载完成后才开始渲染页面,如果 CSS放在靠下的位置则会导致浏览器将渲染时间推迟。


二、态资源缓存
我们知道,缓存对于前端性能的优化是十分重要的,在正式发布系统的时候,对于那些不经常变动的静态资源比如各种JS、CSS文件、背景图片等等我们会设置一个比较大的缓存过期时间(max-age),当用户再次访问这个页面的时候就可以直接利用缓存而不是重新从服务器获取,这样不仅可以减轻服务端的压力,还可以节约网络传输的流量,同时用户体验也更好(用户打开页面更快了)。


一般的公司对于静态资源以及缓存的处理方式无非就这么几种:
1、 在静态资源后面加一个版本号 v=1.111

2 、 为了准确的确定文件是否修改,将后面的版本号修改为文件摘要(主要根据文件内容生成的一个值)

以上两种处理方式,都会存在一些问题。

第一种方式,需要维护版本号,如果在一个文件中,存在多个资源,那么没有被修改过的资源文件也会被修改版本号,导致不必要的资源加载。

第二种方式,可以精确的发现哪一个文件被修改过。从而要求客户端进行重新加载。但是同样会存在一些问题。


如果先发 html文件: 
那么会导致重新加载资源,但一样还是无法访问到最新的特性。(毕竟资源文件还没有真正的更新。),如是Html页面的结构有更新,但加载了旧的资源,很有可能导致页面结构的错乱。并且会缓存资源,直到资源过期,否则除非强制刷新,会一直是错误页面。(这里要注意到,由于第一次加载了旧的资源,版本号又是新的版本号,所以即使在这之后上了资源,这里依旧会读取旧的资源。


如果先发资源文件: 
如果之前访问过页面,那就会有保存有本地缓存,那么由于访问的还是缓存文件,不会出现问题。但如果是新用户,那么就会访问到新的资源文件,很有可能导致页面错乱。而等到页面html也发布之后,页面又恢复了正常
所以还是建议大家通过nginx来配置静态资源缓存过期时间来处理,大概配置如下:
location ~ .*\.(js|css)$ {
expires 30d;
}


三、异步执行 (inline)内部脚本
inline脚本对性能的影响与外部脚本相比,是有过之而无不及。首先,与外部脚本一样, inline脚本在执行的时候一样会阻塞并发请求,除此之外,由于浏览器在页面处理方面是单线程的,当 inline脚本在页面渲染之前执行时,页面的渲染工作则会被推迟。简而言之, inline脚本在执行的时候,页面处于空白状态。鉴于以上两点原因,建议将执行时间较长的 inline脚本异步执行,异步的方式有很多种,例如使用 script元素的defer 属性(存在兼容性问题和其他一些问题,例如不能使用 document.write)、使用setTimeout ,此外,在HTML5中引入了 Web Workers的机制,恰恰可以解决此类问题。


四、(Lazy Load)异步加载 Javascript
随着 Javascript框架的流行,越来越多的站点也使用起了框架。不过,一个框架往往包括了很多的功能实现,这些功能并不是每一个页面都需要的,如果下载了不需要的脚本则算得上是一种资源浪费 -既浪费了带宽又浪费了执行花费的时间。目前的做法大概有两种,一种是为那些流量特别大的页面专门定制一个专用的 mini版框架,另一种则是 Lazy Load。YUI 则使用了第二种方式,在 YUI的实现中,最初只加载核心模块,其他模块可以等到需要使用的时候才加载。


五、异步请求 Callback
异步请求 Callback(就是将一些行为样式提取出来,慢慢的加载信息的内容)在某些页面中可能存在这样一种需求,需要使用 script标签来异步的请求数据。类似:

像以上这种方式直接在页面上写 <script>对页面的性能也是有影响的,即增加了页面首次加载的负担,推迟了 DOMLoaded和window.onload 事件的触发时机。如果时效性允许的话,可以考虑在 DOMLoaded事件触发的时候加载,或者使用 setTimeout方式来灵活的控制加载的时机。

六、减少不必要的 HTTP跳转
对于以目录形式访问的 HTTP链接,很多人都会忽略链接最后是否带 ’/',假如你的服务器对此是区别对待的话,那么你也需要注意,这其中很可能隐藏了 301跳转,增加了多余请求。
避免重复的资源请求
这种情况主要是由于疏忽或页面由多个模块拼接而成,然后每个模块中请求了同样的资源时,会导致资源的重复请求

七、减少cookie传输
一方面,cookie包含在每次请求和响应中,太大的cookie会严重影响数据传输,因此哪些数据需要写入cookie需要慎重考虑,尽量减少cookie中传输的数据量。另一方面,对于某些静态资源的访问,如CSS、script等,发送cookie没有意义,可以考虑静态资源使用独立域名访问,避免请求静态资源时发送cookie,减少cookie传输次数。


八、减少iframe数量
使用iframe要注意理解iframe的优缺点:
优点
可以用来加载速度较慢的内容,例如广告。
 安全沙箱保护。浏览器会对iframe中的内容进行安全控制。
 脚本可以并行下载


缺点
 即使iframe内容为空也消耗加载时间
 会阻止页面加载

本章节我们用页面级来介绍如何进行性能优化,下个章节会从服务器和代码级来详细介绍前端优化的实践和案例。请持续关注!

恒生技术之眼原创文章,未经授权禁止转载。详情见(点击)转载须知

未经授权禁止转载,详情见转载须知

联系我们

恒 生 技 术 之 眼