求知 文章 文库 Lib 视频 iPerson 课程 认证 咨询 工具 讲座 Modeler   Code  
会员   
 
  
 
 
     
   
分享到
高性能网站建设的最佳实践
 
作者 東_康 的BLOG, 火龙果软件 发布于:2014-01-24
 

为了让网页响应速度更快Exceptional Performance团队列出了一系列的最佳实践,包括35个最佳实践条目,分成7种类型类。

最少化HTTP请求

前端占用端用户响应时间的80%。大多数时间被限制在了加载页面的所有组件上:包括图像、样式表、脚本以及Flash等。减少页面组件的数目会相应的减少为了渲染页面而发送的HTTP请求的个数。这是快速响应页面的关键。

一种减少页面组件个数的方法就是简化网页的设计。然而有没有一种方法构建富页面同时又能实现快速响应呢?这里有一些基本的减少页面HTTP请求数的技术同时又能支持富页面的设计。

合并文件 是通过合并所有的脚本文件为一个文件的方法来减少页面的HTTP请求数量的,类似的合并所有的CSS文件也是同样的道理。当脚本文件和样式文件随着页面的不同而不同的时候,合并文件也就充满了挑战。但是在你发布的页面中完成这部分要求会提升响应时间。

CSS Sprites 是减少图像请求数量的一个较好的选择。将背景图像合并为一个单一的图像,并且使用background-image和background-position属性来展示期望的图像片段。

图像映射 通过将多张图像合并为一个,总体文件大小是相同的,但是减少了页面的请求以提升页面响应时间。图像映射仅仅在当页面中的图形相邻的时候起作用,比如导航栏处。定义图像映射的坐标或许会很无聊也容易出错。使用图像映射来进行导航并不容易,所以这个不是推荐的方法。

内联图像 使用data:URLscheme来将图形数据嵌入到实际的页面之中。这个或许会增大你HTML文档的大小。通过将内联的图像合并到你的样式文件中(可能被缓存)是减少HTTP请求并且避免增加文件体积的方法。然而内联图像并不被所有的主流浏览器所支持。

减少页面中的HTTP请求数目仅仅是个开始。对首次访问的用户而言,这是最重要的提升页面性能的方法。就像Tenni Theuer的博客Browser Cache Usage - Exposed!中所描述的那样,每天占总数40%-60%的网站访客是不携带缓存内容访问的。使得页面更快响应这些首次访问的用户是达成更佳的用户体验的关键。

使用内容分发网络

用户和web服务器的接近程度对响应时间有着及其重要的影响。部署你的网站到多个在分布在各地的服务器将使得你的页面对用户而言加载的更快。但是你该如何开始呢?

第一步,为了实现地理位置的内容分发,不要尝试重新设计你的web应用以让他们工作在一个分布式的系统中。依据应用的不同,改变架构可能导致诸如同步会话状态以及在所有服务器端尽心的数据库中进行数据复制事务这样让人烦恼的工作。尝试去减少用户和你的网页内容的距离很可能会被应用的架构流程而延误。

记住80%-90%的端用户响应时间用于加载页面中的各个组件,包括图像、样式表、脚本以及Flash等上。这是性能黄金法则,而不是开始尝试重构应用这样的艰巨的任务,最好事先分发静态的内容。这不仅能够很大程度上减少响应时间,而且也是最大限度的利用了内容分发网络的特性。

内容分发网络(CDN)是一个分布在不同位置的服务器集群,以更佳高效的分发内容给用户。依据测量用户和网络之间的接近程度,特定的用户将被分配到最接近的服务器来接受分发的内容。比如,具备最少网络跳数或者最短响应时间的服务器将被选中。

一些大的互联网公司有他们自己的CDN,但是使用CDN的服务提供商也是很省事的,比如AkamaiTechnologies, EdgeCast,或者level3。对于一个刚起步的公司和私有的网站,租用CDN服务的成本是很高昂的,但是随着你的目标群体的逐渐增多并且分布到四面八方,利用CDN服务实现快速响应用户目的就很必要了。在雅虎,将静态的资源放置到CDN服务上(通过第三方的CDN服务以及雅虎自己的CDN)将用户响应时间缩减了20%或者更多。切换到CDN服务相对来说很容易,而这将极大的提升你的网站的速度。

增加Expires或者Cache-Control头部

这个法则涵盖两个方面:

对于静态的组件:通过设定Expires头部实现“Never expire”策略

对于动态的组件:使用适当的Cache-Control头部来帮助浏览器有条件的请求

Web页面的设计变得越来越丰富,这也意味着更多脚本、样式表、图像和Flash将存在于页面之内。首次访问的用户或许会发送多个HTTP请求,但是使用Expires头部使得你让这些组件可以被缓存。这避免了在后续的页面浏览中发送不必要的HTTP请求。Expires头部通常使用在图像资源上,但是他们也应该被使用在包括scripts、stylesheets以及flash等所有的组件之上。

浏览器(和代理)使用缓存来减少HTTP请求的数量和大小,使得页面更快的被加载。Web服务器在HTTP响应报文中使用Expires头部告诉客户端一个组件可以被缓存的时间长度。下面是一个Expires头部,告诉浏览器这个响应报文在2010年5月15日之后将过期。

Expires:Thu, 15 apr 2010 20:00:00 GMT

如果你使用的是Apache服务器,使用ExpiresDefault 直接依据当前的时间设置一个过期时间。下面的ExpiresDefault的例子设置过期时间为自请求后的十年。

ExpiresDefault“access plus 10 years”

记住,如果你使用一个时间久远的过期时间后,无论何时当你想改变组件的内容必须通过更改文件的名称来实现。在雅虎,我们通常将这一部分作为构建过程的一部分:一个版本号被嵌入到组件的文件名中,比如yahoo_2.0.6.js。

使用长过期Expires头部仅仅影响已经访问你网站的用户。对于首次访问或者缓存为空的用户并没有任何的影响。因此这部分的性能提升取决于你的用户携带填装好的缓存访问你网站的频率。(填装好的缓存包含所有页面组件的缓存)我们在雅虎的网站上测量了这个数据,发现携带填装好的缓存的页面浏览量占总数的75%-85%之间。通过使用长过期的Expires头部,你增加了浏览器中缓存的组件的个数并且在后续的页面展现中重新使用了这些缓存,而不发送哪怕一个字节的请求。

使用Gzip压缩组件

HTTP的请求和相应报文在网络上传输所占用的时间可以通过前端工程师所做出的决策被显著的减少。尽管用户的带宽、英特网服务提供商、同等交换点之间的接近程度在开发的团队的控制之外,但是也有一些其他的变量影响着响应时间。压缩方法通过减少HTTP响应报文的大小来提升响应时间。

从HTTP/1.1开始,web客户端通过Accept-Encoding头部来支持在HTTP请求中的压缩控制。

Accept-Encoding: gzip, deflate

如果web服务器在请求中检测到了这个头部,会通过客户端所列出的方法之一来压缩响应报文。Web服务器通过在响应报文中的Content-Encoding头部来通知web客户端压缩信息:

Content-Encoding:gzip

Gzip是当前最为流行和高效的压缩方法。由GNU项目开发而来,在RFC1952中被标准化。你或许能看到的仅有的其他压缩格式是deflate,但是它的效率要较低,并且也不如GZip流行。

Gzip压缩一般能将响应报文的大小减少70%。接近90%的浏览器端网络交换传输报文声称支持gzip。如果你是用Apache,gzip的配置模块依赖于你的版本:Apache1.3使用mod_zip而Apache2.x使用mod_deflate。

在浏览器和代理之间存在广为人知的问题,导致浏览器所期望的内容和从代理上接受到的压缩后的内容不一致。幸运的是,这些边界情况随着老实浏览器的淘汰而逐渐减少。Apache模块自动通过添加适当的响应头部来帮助解决问题。

服务器端依据文件类型来进行gzip压缩,但是这样在决定压缩什么内容方面又存在太多限制。大多数网站压缩HTML文件。压缩脚本和样式文件也是很值得的,但是很多的web站点并未如此。实际上,压缩包括XML和JSON在内的任何文本响应是很值得的。图像和PDF文件不应该被压缩,因为他们实际上已经被压缩了。尝试使用gzip压缩他们仅仅会浪费CPU资源却又有可能潜在的增加文件的尺寸。

通过Gzip压缩尽可能多类型的文件是减少页面尺寸和加速用户体验的简单方法。

将样式文件放在页面顶部

在研究雅虎的页面性能上,我们发现移动样式文件到文档的HEAD标签内会让页面更快的加载。这是因为将样式表置于HEAD中使得页面渐进的渲染。

关心性能的前端工程师想让页面渐进的加载,也就是我们想让浏览器尽快的展示已经加载的内容。对于含有大量内容的页面以及处于低速网络环境的用户而言,这尤其的重要。给用户视觉上的反馈的重要性已经被人们研究过,比如进度条的提示。对我们而言,HTML页面就是进度指示器!当浏览器渐进的加载页面,头部、导航栏、位于头部的logo等所有的都是对等待页面的用户的视觉反馈。这提升了整体的用户体验。

将样式文件放在文档底部的问题在于这阻碍了在大多数浏览器中的渐进页面渲染过程,包括IE。浏览器阻塞渲染以防止当样式改变时重新绘制页面元素。用户被卡在一个白色的页面中。

HTML标准清楚的陈述了样式文件应该放在HEAD标签中:“与A标签不同,link应该只在文档的HEAD部分出现,不管它出现几次”空白页面或者未样式化的Flash内容都不值得这样的冒险。最佳的解决方案是遵守HTML的规范,在页面的HEAD标签中加载样式文件。

将脚本文件置于底部

脚本文件的问题在于它阻碍了并行加载。HTTP/1.1规范建议浏览器避免在同一个域名下并行加载超过两个组件。如果你将图片置于多个域名下,你可以实现超过两个的并行加载。当一个脚本文件加载的时候,浏览器将不会开启其他的加载,即使对于不同的域名也同样。

在有些情况下,将脚本文件移动到底部并不容易。例如,如果脚本使用document.write来插入部分的页面内容,它就不能被置于页面中更低的位置。这里也有范围的问题。在很多情况下,有很多方法来绕开这些情况。

一个可选的建议是使用延迟脚本,DEFER属性表明这个脚本文件不包含document.write,并且对浏览器来说是可以继续渲染的线索。不幸的是,Firefox不支持DEFER属性。在IE中脚本可以被延迟处理,但和预期的也有差距。如果一个脚本可以被延迟,它也就可以被移动到页面的底部。这会让你的页面加载的更快。

避免使用CSS表达式

CSS表达式是强大的并且也是危险的动态设置CSS属性的方法。他们在IE5之后的版本中被支持但是在IE8中被遗弃。举个例子,background属性可以通过CSS表达式被设置为每小时轮换:

Background-color:expression((newDate()).getHours()%2 ? ”#ffffff” : ”#eeeee”);

可以看到,表达式方法可以接受JavaScript表达式。CSS属性被设置为JS表达式的执行结果。表达式方法会被其他的浏览器所忽略,所以在IE中设置变化属性而在其他的浏览其中设置一致的体验时,这就很有用了。

表达式的问题是他们被执行的频率超出了很多人的预期。不仅仅在页面被渲染和调整大小的时候,也在页面滚动的时候,甚至在用户在页面中移动鼠标的时候。添加计数器使得我们可以跟踪CSS表达式在什么时候以及以什么样的频率执行。在页面中移动鼠标可以很容易导致超过10000次运行。

一个减少CSS表达式的执行次数的方法是使用一次性表达式,也就是在第一次执行的时候将杨树属性设置为一个确定的值,以替代CSS的表达式。如果样式属性必须在页面的整个展示过程中被动态的设置,使用事件侦听器而不是CSS表达式是一个可选的途径。如果你必须使用CSS表达式,记住他们或许会被执行成千上万次并且会影响页面的性能。

使用外联的JavaScript和CSS

很多性能规则涉及到外联的组件如何被管理的。然而,在考虑这些问题之前应该思考一个基本的问题:JavaScript和CSS应该包含在外联的文件中还是内联到页面内部?

在真实的环境中使用外联文件通常能够提升页面加载速度,因为JavaScript和CSS文件可以被浏览器缓存。内联在HTML文件中的JavaScript和CSS在每次被请求的时候加载。这减少了HTTP的请求数,但是却增加了HTML文件的大小。另一方面,如果JavaScript和CSS位于被浏览器缓存的外联文件中,HTML文件的大小也就会减少同时又不增加HTTP的请求数。

关键的因素在于相对于HTML文档请求个数,外联JavaScript和CSS组件被缓存的频率。这个因素尽管难以被具体的量化,但是可以通过几个指标来测量。如果网站的访客在每个会话中访问了多个页面并且你网页中大部分的页面重用了同样的脚本文件和样式文件,这种情况下从缓存中获取外联文件就具备了潜在的好处。

很多Web站点符合这些指标。对于这些站点,最好的解决方案通常是将JavaScript和CSS文件部署成外联的文件。唯一的例外是,在首页中内联脚本和样式是更好的方案,比如雅虎的首页和My Yahoo!.。首页在每次会话中往往具有较少的页面访问量,将脚本和样式内联到页面中会减少页面的响应时间。

首页往往是很多页面访问量中的入口,在内联的过重中有相应的技术手段来减少HTTP的请求数,这方面和利用外部文件的缓存机制得到的好处类似。其中一个方案是将JavaScript和CSS内联到首页中,但是在页面完成加载后动态的加载外联文件。后续的页面将会引用已被浏览器缓存的外联文件。

减少DNS查询

域名系统(DNS)将主机地址映射到IP地址,就像电话本将人名映射到电话号码一样。当在浏览器中输入www.yahoo.com后,浏览器通过DNS解析器获取服务器的IP地址。DNS查询是有代价的,通常需要花费20-200毫秒来查询一个给定域名的IP地址。浏览器在DNS查询完成之前不能够从主机上加载任何东西。

DNS查询被缓存时可以获取更好的性能。可以通过一个特殊的缓存服务器来缓存结果,这个服务器通常由ISP或者本地局域网来维护,但是在用户的个人电脑上也会有缓存。DNS信息会保留在操作系统的DNS缓存区(在Windows上就是“DNS客户端服务”)。大多数浏览器有它们自己的缓存,独立于操作系统的缓存。只要浏览器保留DNS查询记录到自己的缓存区,也就不会向操作系统发出查询请求。

IE会默认缓存DNS查询30分钟,可以通过DnsCacheTimeout注册表来设置。Firefox缓存DNS查询1分钟,通过network.dnsCacheExpiration配置项来控制。(Fasterfox将这个时间延长到了1小时)

当客户端DNS缓存为空(在浏览器层面和操作系统层面),DNS查询数和页面中独立域名的个数相同。这包含页面URL、图像、脚本文件、样式表文件、Flash文件等所使用的域名。减少独立域名个数也就减少了DNS的查询个数。

减少独立域名个数潜在的减少了页面中并行加载的个数。避免DNS的查询会加快页面的响应,但是也会减少并行请求的个数,这就提升了页面的响应时间。我的方案是将页面组件至少分配给2个域名并且不超过4个域名。这样在减少DNS查询个数和运行多个并行加载之间达成折衷。

减小JavaScript和CSS

减小操作是将不必要的字符从代码中移除从而减少页面尺寸提升加载时间。当代码被减小后,所有的注释都被移除,不必要的空字符(空格,空行以及制表符)也同样被移出。有两个流行的工具来减小JavaScript文件尺寸,JSMin和YUI Compressor。YUI Compressor 可以用来减小CSS文件尺寸。

模糊处理也是对源代码的可选优化。这比减小处理要复杂因此也更容易产生bug。在一项针对美国前十的网站做出的调查中,减小操作实现将请求内容大小减少21%相比与模糊处理将请求内容大小减少了25%。尽管模糊处理能够更好的压缩代码,减小JavaScript代码所承担的风险更小。

除了最小化外部脚本和样式文件外,内联的<script>和<style>块可以也应该进行减小处理。即使你使用gzip压缩脚本和样式,减小他们仍然可以将页面尺寸减少5%。随着使用的JavaScript和CSS文件的增大,减少代码也就能获得更多的收益。

避免重定向

使用301和302状态码可以完成重定向。这里有个301响应的HTTP报文头部实例:

HTTP/1.1 301 MovedPermanently
Location: http://example.com/newuri
Content-Type: text/html

浏览器自动的将用户引导到location字段所指定的URL地址。所有的重定向的必要信息都位于报文头部,报文体通常为空。301和302响应并不被缓存除非添加像Expires或者Cache-Controll的报文头部以指定浏览器需要进行缓存。Meta刷新标签和JavaScript是其他的将用户引导到不同URL地址的方法,但是如果你必须重定向,更好的方式是使用3xx的HTTP状态码,这样可以确保浏览器返回按钮工作正常。

需要关注的是重定向减慢了用户的体验。将重定向插入到用户和HTML文件中会减慢页面中的一切,这是由于直到HTML文档完成加载,页面中没有可以被渲染的东西也没有组件开始被渲染。

一个经常发生的耗费的重定向并且却通常会被开发者疏忽的问题如下。URL地址末尾本该有的斜线丢失时就会出现这种问题,例如,访问http://astology.yahoo.com/astrology 会导致301响应的发生,将页面重定向到http://astology.yahoo.com/astrology/(注意添加了斜线)。在Apache中通过使用Alias或者mod_rewrite或者DirectorySlash来修正。

连接遗留网站到一个新的网站是重定向的另一个应用。其他的情况包括连接网站中不同的部分和基于特定的条件(浏览器类型,用户账户)将用户引导开。使用重定向来连接两个网站是件容易的事并且需要很少的额外编码。尽管在这些情况下使用重定向减少了开发者的工作量,这也降低了用户的体验。可选的方式是当两套编码位于同一个服务器上时,使用Alias和mod_rewrite来完成重定向。如果是因为域名更改而需要重定向,一个可选的方法是创建CNAME记录(创建别名从一个域名指向另一个域名的DNS记录)结合Alias和mod_rewrite来实现。

移除重复的脚本文件

在一个页面中包含同样的JavaScript代码两次会降低性能。你想这种事情可能并不常发生。在对美国前十的网站的一个检查是发现他们中的两个包含重复的脚本文件。两个主要的因素增加了单页面中脚本文件重复的偶然性:团队规模和脚本文件的个数。当这样的是发生时,重复的脚本元素通过创建不必要的HTTP请求以及浪费JavaScript的执行资源来降低性能。

不必要的HTTP请求会在IE中发生,然而在Firefox中却没有。在IE中,如果外联的脚本文件被包含两次并且未被缓存,IE就在页面渲染的过程中产生两个HTTP请求。即使脚本文件被缓存了,额外的HTTP请求在页面重新加载的过程中也会发生。

除了产生不必要的HTTP请求,脚本的多次执行也浪费了时间。多余的JavaScript执行会在IE和Firefox中同时发生,而不管是否缓存。

一个避免意外的包含两次同样的脚本的方法是实现你的模板系统中的脚本管理模块。典型的方法是在HTML页面中使用Script标签。

<script type=”text/javascript”src=”menu.js”></script>

在PHP中可以借助于调用insertScript函数来实现

<?php insertScript(“menu.js”)?>

除了防止同脚本文件被插入两次,这个函数可以处理其他的脚本问题,比如依赖检查以及为脚本的文件名中添加版本号以支持长时间过期的头部(ExpiresHeader)。

配置ETags

实体标签(ETags)是web服务器和浏览器用来判断缓存中的组件和源服务器中的组件是否匹配的机制。(“entity”是“component”的另一种表述:图像,脚本,样式等)ETags通过提供比last-modified日期更富有弹性的机制来验证实体。一个Etag就是一个唯一标示一个组件版本的字符串。唯一的格式限制是这个字符串必须使用引号来标注。源服务器通过ETag响应报文头部来指定组件的ETag。

HTTP/1.1 200 Ok
Last-Modified: Tue, 12 Dec 2006 03:03:59 GMT
ETag: “10c24bc-4ab-457e1c1f”
Content-Length: 12195

稍后,如果浏览器不得不验证组件,将使用If-None-Match头部将ETag传回到源服务器。如ETag匹配,304状态码将会被返回也就减少了响应报文12195字节。

GET /i/yahoo.git HTTP/1.1
Host: us.yimg.com
If-Modified-Since: Tue, 12 Dec 2006 03:03:59 GMT
If-None-match: “10c24bc-4ab-457e1c1f”
HTTP/1.1 304 Not Modified

ETags的问题在于它们使用特殊属性来构建,使得它们只对应于一个特定的服务器。当从一个服务器上得到一个源组件稍后又从另一个不同的服务器上去验证的情况下,ETag是不会匹配的,这种情况在使用服务器集群处理请求的Web站点中是很普遍的现象。默认情况下,Apache和IIS都为ETag嵌入了数据奇迹般的较少了验证过程中的奇怪特性,这在基于多个服务器上的web站点上测试可行。

Apache1.3和2.x上ETag格式是inode-size-timestamp,尽管在多个服务器上会处于同一个目录下、有着同样的尺寸、许可、时间戳等。inode却随着不同的服务器而不同。

II5.0和6.0在ETags上有相似的问题。ETags在IIS上的格式是:Filetimestamp:ChangeNumber。“ChangeNumber”是用于记录对IIS的配置改变。在一个Web站点的所有的IIS服务器上,ChangeNumber一般是不同的。

结果就是由Apache和IIS为一个相同的模块产生的ETags在不同的服务器上并不匹配。如果ETags不匹配,用户也就不会获得简短快速的304响应,而那恰恰是ETags被设计出目的;取而代之的是他们将会获取一个正常的200响应,这个响应携带了所有的组件数据。如果你仅仅在一个服务器上管理你的站点,这不是问题。但是如果你有多个服务器来管理站点,并且使用Apache和IIS的默认ETag配置,你的用户就获得了更慢的页面加载,你的服务器负荷也会加重,你浪费了更多的带宽,并且代理也不会很好的缓存你的内容。即使你的组件有长时间过期的头部,一个有条件GET请求也会发出无论用户是点击了重新加载或者刷新按钮。

如果你没有利用ETags所提供的所有的弹性验证模型,最好直接移除ETag机制。Last-Modified头部依据组件的时间戳来验证。移除ETag会在相应报文和后续的请求中减少了HTTP头部的大小。这篇MicrosoftSupport文章介绍了如何移除ETags。在Apache中,在配置文件中仅仅加入下面这句话就能移除ETags:

FileETag:none

让Ajax请求可被缓存

Ajax的一个广为人知的好处就是它为用户提供了即时的反馈,因为它异步的从后端服务器上加载信息。然而使用Ajax可能使得用户为了等待异步的JavaScript和XML响应而开始把玩他的大拇指,用户是否一直等待取决于Ajax如何被使用。例如,在一个基于Web的邮件客户端中,用户一直等待Ajax的请求结果从而找到他们想要查询的Email信息。记住异步并不意味着实时。

为了提升性能,优化Ajax的响应至关重要。最重要的一个途径是将请求结果缓存起来,就像在“添加Expires和Cache-Contoll”一节中讨论的那样。一些其他的规则也适用于Ajax:

Gzip压缩组件

减少DNS查询

压缩JavaScript

避免重定向

配置ETags

让我们看看一个例子。一个Web2.0的邮件客户端或许自动使用Ajax来加载用户的地址簿。如果自从上次使用email web程序以来,用户没有改变地址簿,之前的地址簿响应数据可以从缓存中读取,前提是Ajax缓存通过使用Expires或者Cache-Contolle头部实现。浏览器必须被通知什么时候使用之前的缓存地址簿数据或者请求新的数据。这可以通过为地址簿的Ajax地址添加时间戳来标注最后一次用户更改地址簿的时间,从而实现上述目的,例如&t=1190241612。如果地址簿自从上一次加载以来没有被改变,时间戳不变,浏览器会从缓存中获取地址簿数据从而减少了一个HTTP往返请求。如果用户改变了地址簿数据,时间戳确保一个新的URL地址和缓存中的响应不同,浏览器就向更新的地址簿入口请求数据。

尽管Ajax响应报文被动态的创建,但仅仅对单户起作用,他们可以被缓存。这将使你的Web2.0应用的更快。

尽早的清除缓存

当用户请求页面,无论如何需要200-500ms的时间让后端服务器生成一个页面。在这个时间内,浏览器因为等待数据的到大而处于空闲的状态。在PHP中有flush函数。它能够将部分准备好了的HTML响应发送给浏览器,使得浏览器可以开始加载组件而你的后端服务器忙于生成其他的HTML页面。在忙碌的后端和松闲中都可以看到这样做的好处。

一个添加好的flushing的地方就在HEAD标签后,因为html的Head标签通常容易生成,并且允许包好任何的CSS和JavaScript文件,这样浏览器开始并行请求数据而后端仍然在处理其他的东西。

例如:

…<!—css, js-->
</head>
<?php flush();?>
<body>
…<!—content-->

Yahoo! Search主导了这方面研究并且在真实的用户上进行测试,从而证明使用这项技术的好处。

用GET发送AJAX请求

Yahoo!Mail团队发现当使用XMLHttpRequest时,POST在浏览器中经历了两个步骤的处理:先发送请求头部,然后发送数据。所以最好使用GET,仅仅使用一个TCP数据包发送请求(除非你有很多的cookies)。在IE中最大URL长度是2K,如果发送多于2K的数据,你就不能使用GET。

一个让人感兴趣的个例是POST如果没有发送任何数据的时候的行为类似于GET。基于HTTP的规范,GET为的是获取信息,所以在仅仅只是请求数据的时候使用GET是有意义的(从语义上),而和向服务器端发送数据则不同。

延迟加载模块

你可以仔细看下你的页面,问问自己:“为了完成页面的起始渲染,哪些是绝对需要的?”其余的内容和组件可以稍后加载。

JavaScript是很适合置于onload事件触发之前或之后。例如,如果你使用JavaScript或者库来完成拖动和动画的任务,这些可以等待。因为在页面上拖动元素在初始渲染之后才可用。其他的可以在加载事件之后获取的包括隐藏的内容(在用户的某个操作后出现的内容)或者图片折叠。

有些工具可以帮助你达成目的:YUI Image Loader允许你延迟加载位于折叠区域之后的图片并且YUIGet Utility是一个简单的方法来实时加载JS和CSS。例如可以使用Firebug面板看看Yahoo!Home Page的加载状况。

当性能目标和其他的web开发最佳实践相关联时。这种情况下,渐进增强的方法告诉我们JavaScript可以提升用户体验,但是你不得不确认页面在没有JavaScript的情况下也是可用的。所以当你确认页面工作良好,你可以通过一些后加载的脚本提升页面,随后当你的拖动效果和动画效果发挥效应时会给你带来跟多的喝彩。

为了让网页响应速度更快Exceptional Performance团队列出了一系列的最佳实践,包括35个最佳实践条目,分成7种类型类。

预先加载组件

预先加载看上去像是延迟加载的反面,但是它实际上有着不同的目的。通过预先加载组件,你可以利用浏览器的空闲时间并且可以请求将来需要的组件(比如图像、脚本以及样式)。这样当用户访问下一个页面的时候,你的大多数组件也就位于缓存之中了,你的页面加载对于用户来说就很快。

实际上有几种的预先加载类型

1、无条件的预先加载——一旦onload时间触发了,你继续去加载其他的组件。看下Google.com是如何加载sprite图像的。在google首页并不需要的Sprite图像会被加载,因为在后续的搜索页中这些Sprite图像是确实需要的。

2、条件预加载——基于用户的动作,你预算用户去往哪里并且相应的进行预加载。在search.yahoo.com中,你可以看到一些额外的组件在用户开始在输入框中输入东西的时候被加载。

3、期望的预加载——在发起一个重新设计页面之前进行预加载。你经常听到在重新设计一个页面之后的反馈:“新站点很好,但是这比之前的要慢。”部分问题的原因是用户携带全量的缓存访问你的老页面,但是访问新的页面时缓存却为空的。你可以通过在最终发布重新设计的页面之前预先加载一些组件来缓解这个副作用。在你的老页面中利用浏览器空闲的时间来请求新页面中使用的图像和脚本。

减少DOM元素的数量

一个复杂的页面也就意味着需要更多的字节去加载,这也同样以为这在JavaScript中访问DOM元素变得更慢。例如,当你在一个具备500个DOM元素上注册事件和在一个5000个元素上注册事件是完全不同的。

大量的DOM元素意味着页面的标记应该被改进而不必移除内容。你使用包裹表格来完成布局的目的吗?你是否添加了跟多的DIV标签为了解决一个布局问题?或许存在更好的更加符合语义的方法来完成标记。

YUICSS Utilities会很好的帮助布局:grid.css可以帮助你整体布局,font.css和reset.css可以帮助你移除浏览器的默认样式。这有助于你重新更新和思考你的标记,例如,仅在具备语义意义的前提下使用<div>标记,而不是因为需要渲染一个空行。

DOM元素的数量很容易检测,使用Firebug的控制台,输入:

document.getElementsByTagName(“*”).length

问题是多少个DOM元素才算是多呢?检查下类似的具备优良标记的页面。比如Yahoo!Home Page是一个繁忙的页面并且是有不超过700个元素(HTML标签)。(译者注:实际上现在有2123个)

依据域名分割组件

分割组件可以使你最大化的利用并行加载。由于DNS查询的代价,确认你使用不超过2-4个域名。例如可以在www.example.org上管理HTML和动态的内容而将静态的组件分配到static1.example.org和static2.example.org上。

阅读Tnni Theurer和Patty Chi缩写的“Maximizing Parallel Downloads in the Carpool Lane”获取更多信息。

最小化iframes的数量

Iframe允许HTML文档插入到一个父文档中。理解iframe的工作机制有助于他们被有效的使用。

<iframe>的赞成意见

1、有助于处理缓慢的第三方徽标和广告

2、安全沙箱

3、并行下载脚本文件

<iframe>的反对意见

1、如果为空的话会很浪费资源

2、阻碍页面的onload

3、非语义化的

不要有404响应

HTTP请求在发送一个请求并得到一个无用的响应方面的代价很高(也就是404 Not Found),并且会减慢用户的体验而不带有任何好处。

一些站点有帮助性质的404页面“你的意思是X?”,这提升了用户的体验但是也浪费了服务器资源(比如数据库)。特别的,当连接到一个外联的JavaScript文件并且结果是404的情况尤其的糟糕。首先,这个加载会阻碍其他的并行加载。其次,浏览器或尝试解析404响应报文体就如同它是JavaScript代码一样,并且尝试去发现一些没用的信息。

减少Cookie的大小

HTTPcookies使用在广泛的场景中,比如授权和个性化。Cookies的信息通过HTTP头部的标签在浏览器和服务器端传输。保持Cookie尽可能的小是很重要的,这能够影响用户的响应时间。

阅读 Tenni Theurer和Patty Chi的“When the Cookie Crumbles”获取更多消息。结论如下:

1、减少不必要的cookies

2、让cookies的大小尽可能的小,从而最小化对用户的响应时间的影响。

3、注意在一个适当的域名级别设定Cookie使得子域名不熟影响

4、适当设置过期时间。一个较早的过期时间会尽快的移除cookie从而提升响应时间

为组件使用免cookie的域名

当浏览器发请求静态的图像会把cookies一起发送出去,而服务器端却不需要这些cookies。所以他们在网络上传输也是不必要的。你应该确保静态的组件在求请求时不要携带cookies信息。创建子域名并且将所有的静态资源部署到那。

如果你的域名是www.example.org,你可以在static.example.org中管理静态资源。然而如果你已经在顶级域名example.org上设置了cookies,而不是www.example.org,那么所有的请求到static.example.org将会包含cookies。这种情况下,可以通过购买一个新的域名,在那里管理你的静态资源,并且使得这个域名不含有cookies。雅虎使用yimg.com,Youtube使用ytimg.com,Amazon使用images.amazon.com以及其他。

另一个在无cookie的域名中管理静态的资源的好处是,一些代理或许会拒绝缓存具有cookies信息的组件。注意下,如果你在想是否需要使用example.org或者www.example.org设置你的首页地址,考虑下cookie的影响。忽略www让你只有将cookies写到*.example.org上,所以为了性能的缘故最好使用www的次级域名,并且将cookies写到相应的次级域名下。

最少化DOM访问

通过JavaScript来访问DOM元素很慢,所以为了更快的响应页面,你应该:

1、缓存对已访问的DOM元素的引用

2、离线更新节点然后将它们添加到树中

3、避免使用JavaScript修复样式问题

阅读 YUI theatre的文章“High Performance Ajax Applications”了解更多。

开发敏捷的时间侦听器

有时候,页面因为将太多的时间侦听器注册到DOM树中的不同的元素上而变得迟钝。这也是为什么使用事件代理是个好的途径。如果你有10个按钮位于一个DIV中,仅将一个事件侦听器注册到包裹对象DIV中,而不是为每个按钮注册事件。事件冒泡让你可以捕获事件并且知道它是哪个按钮元素发送的。

你也不必等待为了开始对DOM树做一些操作而等待onload事件。通常你所需要访问的仅仅是已经在树中的严肃。你不用等待图像的加载。DOMContentLoaded是你需要考虑的问题不是onload事件。你可以使用YUIEvent工具,有个onAvailable方法可以实现这些。

阅读Julien Lecomte的“High Performance Ajax Applications”了解跟多。

使用<link>而不是@import

之前的最佳实践之一就是CSS应该被置于最顶端,从而实现渐进渲染。

在IE中@import和将<link>置于页面最低端的效果类似,所以最好不用这个功能。

避免滤镜

在IE7一下的版本中,IE所特有的AlphaImageLoader滤镜致力于修正PNG图像半透明的问题。这个滤镜的问题是,当图像加载过程中它阻塞了渲染并且固化了浏览器。它也增加了内存的消耗,针对每个元素执行,而不每张图像,所以问题多多。

最好是避免使用AlphaImageLoader而是使用更优雅的PNG8,可以在IE中工作。如果一定要是用AlphaImageLoader,使用下划线的hack方法_filter使得不影响IE7以上的用户。

优化图像

当设计师为你的页面创建完图像后,在你将图像FTP到服务器上之前,你仍然可是试试一些方法来优化。

l你可以价差GIF图像,看看他们使用的画板颜色数是否和图像中的颜色树一致。使用imagemagick可以很容易检出
Identify –verbose image.gif

当你使用4个颜色,而画板中有256色,这样也就有了提升的空间。

1、尝试将GIF图像转化为PNG图像,查看是否有减小图像体积。时常的开发者因为浏览器支持优先而犹豫是否使用PNG图像,但是这只是过去的事了。真正的也是唯一问题是PNG图像的alpha透明度,而GIF并非是真色因而不支持透明图。所以任何GIF可以完成的,PNG(PNG8*)也可以完成(除了动画)。下面这个简单的imagemigick命令产生可安全使用的PNG图像
Convert image.gif image.ong

“我们所能说的就是:给PiNG一个机会”

2、运行在你所有的图像上运行pngcrush(或者其他任何PNG优化工具),例如:
Pngcrush image.png –rem alla –reduce –bruteresult.png

3、在所有的JPEG图像上运行jpegtran。这个工具对JPEG图像进行无损处理比如旋转也可以用户优化并且移除注释以及其他的无用信息(比如EXIF信息)。

Jpegtran –copy none –optimize –perfect src.jpg dest.jpg

优化CSS Sprites

1、将图像水平排列而不是竖直排列通常会导致更小的图像体积

2、 结合相似的图像到一个Sprite文件中会使得你的颜色数保存在一个较低的水平,理想情况下少于256色适用于PNG8图像

3、 “对移动设备优化”并且不要在Sprite的图像片段中遗留太多的空间。这对图像的尺寸的影响并不大但是会让用户代理消耗更少的内存来将图像解压到一个像素图像。100*100图像有10000像素,然而1000*1000的图像就有一百万像素。

不要在HTML文档中缩放图像

不要仅仅因为能够在HTML中设置Width和Height而使用比需要的图像更大的图像。如果你需要<imgwidth=”100” height=”100” src=”mycat.jpg” alt=”My Cat”/>,那么图像(mycat.png)应该为100*100像素而不是500*500像素。

让favicon.ico较小并且可以被缓存

Favicon.icon是位于你服务器目录根部的图像。这图像很棘手因为即使你不关心它,浏览器仍然会请求它,所以最好不要针对这个请求回复404 Not Found。同样,既然它位于同一个服务器上,每次发送请求cookies信息也被发送。这个图像同样也会干扰下载队列,例如在IE中,当你在加载时间中请求额外的组件时,favicon将会在其他额外组件加载之前加载。

为了减少favicon.ico的不利影响,确保:

1、它很小,最好小于1K

2、设置Expires头部为你认为的一个合适值(你不能通过重命名来改变它)。以或许可以设置Expires头部为未来几个月。你可以检查当前favicon.icon的最后修改时间以做出合适的决定。

Imagemagick可以帮助你创建小的favicons图像。

使得组件小于25K

这个限制和iPhone不能缓存超出25K组件有关。注意这是为压缩的大小。因为GZIP压缩本省可能并不足够,这也是前面所论述的减小尺寸的重要性所在。

阅读WayneShea 和 Tenni Theurer的“PerformanceResearch, Part 5: iPhone Cacheability - Making it Stick”获取更多信息。

将组件打包到多组件文档中

将组建打包到一个多组件的文档中就如同含有附件的邮件一样,这使得你可以用一个HTTP请求获取多个组件(记住HTTP请求的代价高昂)。当使用这个技术时,首先检查用户代理是否支持(iPhone不支持)。

避免image标签的src属性为空

空的src属性往往让人意外的出现。它以两种方式出现:

1、静态的HTML

<img src=””>

2、JavaScript

var img = new Image();

img.src=””

两种形式引起同样的问题:浏览器向服务器发送一个额外请求

IE将请求发送到页面所在的目录

Safari和Chrome将请求发送到页面本身

Firefox 3和更早的版本和Safari和Chrome的行为类似,但是3.5版本的解决了这个问题,并且不在发送请求

Opera在遇见一个空的src属性时不做任何处理

为什么这种行为很糟糕

1、发送大量的非预期的请求消弱了服务器,特别的对于那些用户浏览量过百万的页面

2、浪费了服务器的计算资源生成了绝不会被访问的页面

3、或许让用户数据奔溃。如果你跟踪请求的状态,无论通过cookies还是其他方式,有可能毁掉用户的数据。即使图像请求并不返回任何图像,所有的头部被浏览器接受,包括所有的cookies。然而其他的响应被舍弃了,或许损害已经产生了。

这种行为的根源在于浏览器解析URI的方式。这种行为在RFC3986——统一资源定位符中被定义。当一个将一个空的字符串作为URI时,将被看作是一个相对地址并且会以及在5.2部分定义的算法解析。在5.4部分定义了空字符串的特性实例。Firefox、Safari和Chrome都正确的依据了规范解析空字符串,但是IE的解析方式是错误的,显然适合规范的一个较早的版本一致,RFC2396-统一资源定位符(这个规范被RFC3986淘汰了)。所以从技术上讲,浏览器做了他们应该做的工作以解析一个相对的URI地址。问题是在这种情况下,空的字符串显然不是故意设置的。

HTML5标签为标签的src属性添加了描述以引导浏览器不要做出额外的请求,这在4.8.2部分被描述了:

src属性必须被呈现,并且必须包含有效的URL,引用那些即未页面化也未脚本化尚未能交互的或者可以后续被动画化的资源以及图像资源。如果基本的URI和文档的地址一直,src属性不能为空。

希望以后浏览器中不在有这样的问题。不幸的是,没有相关的条款应对<script src=””>和<link href=””>。或许为了让浏览器不意外的实现上述的那种行为,我们仍然需要时间来做出调整。


 
分享到
 
 


十天学会DIV+CSS(WEB标准)
HTML 5的革新:结构之美
介绍27款经典的CSS框架
35个有创意的404错误页面
最容易犯的13个JavaScript错误
设计易理解和操作的网站
更多...   


设计模式原理与应用
从需求过渡到设计
软件设计原理与实践
如何编写高质量代码
单元测试、重构及持续集成
软件开发过程指南


东软集团 代码重构
某金融软件服务商 技术文档
中达电通 设计模式原理与实践
法国电信 技术文档编写与管理
西门子 嵌入式设计模式
中新大东方人寿 技术文档编写
更多...