学习《高性能网站建设指南》一书的学习笔记

今天向同事借了一本《高性能网站建设指南》书,是Oreilly的书,真得很不错啊,讲得也很详细,全面的讲述了做为一名前端开发工程师如何来提交Web设计与开发时如何提高性能,如何减少Web流量、如何减少HTTP请求,为了防止以后忘记,特地把本书要点记下来,以备以后查阅,也和大家一起共同分享一下:

先总结一下:

   1、减少HTTP请求。

        (1)、页面在加载的时候有10%-20%的时间是用在了HTML代码的加载,80%-90%的时间是用在了网页外部组件的加载(例如:图片、CSS文件、JavaScript文件等)。

        (2)、在处理网页面图片时有三种方法可以优化并减少HTTP请求。

                 第一种是使用一张合成的CSS图片(这张图片是网页所用的的ICON、背景图片的集合)作为背景,然后通过CSS的背景定位来截取背景相应部分的元素,这样这张合成图片就只占用一个HTTP请求;

                 第二种方法也是使用一张合成图片作为图片,但是这张图片使用图像映射在背景图片上做链接,即<map>元素(个人认为此方法不是特别好);

                 第三种方法是直接在网页里使用内联图片,例如:<img src="data:image/gif; base64 图片的二进制内容",当然图片的二进制内容可以通过后端的来生成然后输出,例如PHP的file_get_contents()然后再进行base64编码来输出。

(3)、合并一些可合并的CSS文件和JavaScript脚本文件,这样减少请求数。

2、使用CDN缓存(缩短HTTP请求的响应时间)

这个就不用说了,没有什么好说的,唯一要说的是CDN只能缓存图片文件、CSS文件、JavaScript文件、Flash文件等静态组件,其它的HTML由于有可能会动态更新,不太适合CDN缓存。

3、在HTTP请求头添加Expires头(减少HTTP请求)

Expires的作用就是告诉浏览器一个组件在浏览器缓存里的过期时间是什么时候,如果当前时间没有超过Expires时间就会从浏览器缓存里读取文件,不管服务器端文件是否更新都不会去检查。这个参数要求本地时间与服务器时间保持同步。

       Cache-Control:max-age=秒数  这个参数的作用是设置一个组件的在浏览器里的缓存时间,这就是为了弥补Expires本地时间和服务器时间必须同步的不足,只要一个组件的缓存时间超过了就会重新去读取。如果Cache-Control和Expires两个同时存在于HTTP响应头里,则Cache-Control的优先级会高于Expires。

    4、对HTTP请求内容进行压缩(减少HTTP请求的大小)

        使用Accept-Encoding:gzip, deflate来标识浏览器对压缩的支持,当Web服务器接收到这个值就会对HTTP响应数据进行相应的压缩后返回给浏览器(前提是Web服务器支持对应的压缩,一般都是用Gzip压缩),在压缩时一般针对的是HTML页面、CSS文件、JavaScript文件、XML数据和JSON数据,其它的如图片、Flash、PDF等文件不能对其进行压缩。目前Apache1.3默认采用mod_zip进行压缩,Apache2.x采用mod_deflate,至于Web Server对压缩的配置这里就不做详细解说了,请有兴趣的童鞋自己Google、Baidu一下。

        代理缓存的问题,当采用压缩时,如果只有浏览器于Web Server通信时可以根据Accept-Encoding来判断是否需要压缩,但是当浏览器使用代理发送请求时就会比较麻烦。一种情况是浏览器发送一个不支持压缩的请求给代理服务器,此时代理服务器将请求转发给Web Server,获得了Web Server未压缩的响应内容,代理服务器将其缓存,那么当另外一个来自于支持Gzip压缩的请求通过这个代理服务器,这时代理服务器就会将缓存里未压缩的内容直接发送给浏览器,这样就失去了进行压缩的机会了;如果反过来就更糟糕了,浏览器发送的支持Gizp压缩的请求给代理服务器,此时如果代理服务器里的缓存是空的就会转发到Web Server从而获得一个压缩过的响应信息,并且将其缓存,而后有一个不支持压缩的浏览器请求到这个代理服务器,代理服务器就会将缓存里压缩过的信息直接发给浏览器,这样就会出现问题了。

        解决这个问题的办法就是Web Server的响应中添加Vary头,Web Server可以告诉代理服务器根据一个或者多个请求头来改变缓存的响应,由于压缩的决定是基于Accept-Encoding请求头的,因此需要在服务器的Vary响应头中包含Accept-Encoding信息。

                      Vary:Accept-Encoding

        这将使代理缓存响应信息的多个版本,一个是未压缩的版本,一个是压缩过的版本。然后根据不同的请求返回给浏览器不同的版本响应信息。

    5、将样式放在页面顶部(加快页面加载时间,避免页面空白)

          无论是内联样式,还是外联样式,都应该把样式放在页面的顶部,甚至一个样式可能跟当前页面无关(例如:一个点击后弹出窗口的样式,但是实例上跟页面加载是没有关系的),我们都知道一个页面的加载方式是自顶向下的,那么按道理来说这个与当前页面加载没有关系的样式放在页尾应该会使页面加载更快呀,为什么呢?我们现在就来说说吧

原因就是出在IE上,因为在IE中,由于页面的加载方式的不同,如果把样式文件放于尾部(虽然这样加载的时候会稍微的快一点),会使页面在加载的时候产生空白,只有等所有组件都加载完成后才会统一显示,这样用户体验就会很糟糕,因为如果一个页面过大,用户要在空白的屏幕前面等待很长的时间,这种白屏的情况在以下三种情况会产生,一种是页面在新窗口打开的时候,另一种情况是页面重新加载的时候,第三种是作为浏览器主页,然后直接打开的时候。

接下来说说将页面放在顶部的情况会是怎样的,把样式文件放在顶部也有两种方式,一种方式是以Link的形式将外部文件引入当前页面,另一种方式是通过样式的import来导入一个样式文件,这里我建议大家使用链接的形式来引入样式文件,import虽然也可以,但是如果没有把import放在样式的最前面,那么可能导致样式文件无法导入进来,link的形式也带来性能上的优势,而且import有可能会导致上述白屏的可能。如果这个样式文件跟当前页面有关,那更应该放在页面的顶部,因为如果放在尾部,页面在加载时没有找到样式文件,而无法渲染,到最后样式文件引进来后,浏览器会重新去重新绘制页面的结构,从而产生了"无样式闪烁"的效果。所以一句话,把样式文件放在顶部是并且以link的形式引入是最明智的选择。

   6、将脚本放在底部(避免脚本阻塞页面加载)

         浏览器在向Web Server 发送HTTP请求是并行发送的,但是HTTP1.1规定只能对同一主机同时并行两个请求,HTTP1.0中Firefox默认的是并行8个请求。并行下载的可以减少下载时间,但是有一点就是脚本是无法并行下载的,因为脚本有可能改变文档树的结构(document.write),并且它必须保证各个脚本的连续性,否则就无法确保哪个文件里的方法先执行,所以脚本会阻塞后续组件的下载,直到这个脚本下载完成。所以如果把所有的脚本都放在页面顶部,那么后续的组件就要等待脚本的下载,页面就会出现空白的情况,所以这里建议将脚本放在页面的底部,除非这个脚本无法放在底部的情况下除外。

 

    7、避免CSS中使用表达式(加快页面的加载)

                   CSS表达式是IE特有的规则,它很好用但是也很危险,它可以在CSS里绑定JavaScript脚本,从而有处理样式的信息,但是它的负面影响也是不容忽视的,这个表达式是动态去执行的,不论是窗口大小的改变还是最小化、最大化窗口时都会执行这个表达式,也就是说这个表达式会频繁的执行。甚至有时候还会导致IE崩溃的Bug出现,所以说它很危险,也许这就是利益与风险并存吧。

         如果你非要用到CSS表达式,那么可以选择折中的方式,即采用“一次性表达式”,就是说把一个JavaScript方法写在<script></script>标签里,然后在CSS表达式里调用这个方法,并把this关健字作为参数传给这个方法,这样就是一个一次性表达式。

         例如:

          <script language="javascript">

                  function setBgColor(elem){

                         elem.style.color =(new Date().getHours()%2 ? '#F3fA30' : '#86D8F3');

                  }

          </script>

          <style>

                .myclass{

                          color:expression(setBgColor(this));

                 }

          </style>

      如果一次性表达式还是不太好的话,那么我建议是完全采用JavaScript事件来处理样式问题,比如可以采用onload方法来处理需要的CSS相关内容。也就是说能避免CSS表达式的尽量避免。

      8、使用外部JavaScript和CSS(减少HTTP请求)

           

           实际上内联的JavaScript和CSS是会比外联的快的,但如果考虑到脚本的重用和缓存问题,那外联的形式就会占有很大的优势,因为在不同的页面都使用到相同的脚本和样式时那么把它们放到外部去就浏览器就会将其缓存,以备后续访问,但是如果有些页面一个用户只会访问一次,那么就可以考虑以内联的形式存放。那么这两种情况如何取舍呢?有一个折中的办法就是如果这个页面只访问一次的效率较高的话可以采用联的方式,然后在页面的末尾采用脚本动态的载入外联的CSS和脚本,这样相当于一个页面就有两份脚本和样式。一个是当前页面采用,另一个是为了让浏览器缓存。当然这种动态缓存可以采用Cookie判断来实现。

      9、减少DNS查找()

           当一个通过一个域名访问一个Web Server的IP时,就需要一个域名查找主机的过程,当DNS缓存为空时,操作系统和浏览器都会缓存DNS信息,但是由于浏览器有自己的缓存,或者用户关闭浏览器时都会清空DNS缓存,幸运的是操作系统也缓存了DNS,也就是说当浏览器没有DNS缓存时,会去查找操作系统的DNS缓存,当ping一个域名时所看到的TTL就是DNS的缓存时间,但是HTTP有一个keep-alive参数,这个参数的作用是让浏览器与保持一个TCP连接,当然当这个连接闲置一分钟后就会自动断开。但它的好处是是可以重用这个连接,也可以减少DNS查找以加快HTTP的响应时间。

      10、精简JavaScript(压缩、精简JavaScript脚本)

            1)、精简:移除脚本中不必要的空格、注释、换行符、制表符等,这样可以减少脚本的文件大小。

            2)、混淆:将变量名、方法名、类名等替换成列为简短的名字,这样也会减少脚本的文件大小(缺点是不易维护、可读性差,反向工程困难)。

            3)、精简后压缩、混淆后压缩:这两种情况据个人理解处理后的大小应该是差不多的,有兴趣的童鞋可以测试一下。

            4)、CSS精简和压缩:CSS也和脚本一样也可以进行精简和压缩以减少文件的大小从而加快加载时间。

      11、避免重定向

             301、302重定向是不会被缓存的,重定向的方式有很多种方式,一种是html的<meta http-equiv="refresh" url="" />重定向;也可以使用JavaScript的location.href来实现重定向,DNS也可以实现重定向。重定向也是不能够并行加载的,当URL后面不有加下"/"是也会产生重定向,但是主机名后没有加上"/"是不会产和重定向的。所以在风锌开发是最好避免重定向。

       12、移除重复脚本

              由于项目的庞大或者开发团队的增加,有可能会在同一个页面多次加载同一个JS代码,这样就导致了代码重复,所带来的就是多了HTTP请求(在IE浏览器中无缓存下出现这种情况)和执行JavaScript的时间,所以一定要确保一个脚本只被包含一次。

        13、配置ETag

 

               配置或者移除ETag来使浏览器缓存(有关于ETag请查看相关资料)

 

        14、使AJAX可缓存

                确保AJAX请求遵守性能指导,尤其应具有长久的Expires头信息

这是本书的大概内容总结,可以说真得写得很不错,外国人的书就是好,推荐大家多看看“图灵系列”和“O'REILLY”出版设的书,好了,快凌晨两点了,要睡了,晚安。