给你的站点全面提速——来自YahooUI的各种BsetPractices



最少化Http请求

终端用户80%的响应时间都花在前端(而非服务端处理)。而这其中绝大部分的时间又都花在下载所有的页面“组件”:图片、样式表文件、脚本文件、Flash等。而按需加载,减少http的请求数来呈现页面,是加快页面呈现的关键。



而减少页面组件的其中一种方式就是简化页面设计。但有一种既呈现富客户端组件,同时又使页面的响应时间得以加快吗?这里有几种技术能够有效减少http的请求数,并且又能够支持富客户端页面的设计。

合并文件:你可以合并多个文件来减少http请求,比如合并脚本文件,简化并合并多个样式文件。当那些脚本或样式文件对各个页面都不尽相同时,这将变得更具挑战,但是把它列入你发布的其中一个步骤吧!

合并图片:这可以有效地减少你对于图片的请求。合并你的背景图片到一个大图片中,并使用CSS的background-image和background-position属性来呈现你最终想要的图片区域。

Image maps,合并多个图片到一个大图片中。图片的总体大小和原来相同,但减少了http请求的数量从而可以为网页加速。Image maps仅仅在图片位于页面中相邻的区域的情况下,才会起作用,例如实现一个导航条。定义image maps的坐标很乏味且容易出错。使用Image maps的导航也不可访问,所以不做过多推荐。

内联图片:使用data:URL scheme来嵌入图片的数据到最终的页面(曾Google有对图片这个做过)。这会导致你HTML文档的大小变大。合并内联图片到你(缓存过)的样式文件中是一种减少HTTP请求以及避免增加你页面大小的方式。内联图片的方式并不为所有现代主流浏览器所主持!

为首次访问的用户提高性能,这是一个非常重要的指引。据统计,40%-60%日常用户访问你的站点的时候,处于一个“零缓存”的状态。所以,确保你的页面对那些首次访问的访问者来说是快速的,这是一个非常重要的用户体验。

 

使用CDN(ContentDelivery Network)

用户接近你的web服务器对于响应时间是有影响的。将你的静态内容分散到多个不同地理区域的服务器上,从用户的角度来看将会觉得你的页面加载地更迅速。但你应该从哪儿开始呢?

首先作为第一步,你应该实现对内容的地理分散,不要企图从新设计你的web应用程序来满足一种“分布式架构”。依赖应用程序,改变架构可能包含着繁重的任务,比如同步Session状态、跨服务器区域来复制数据库事务。企图减少用户和你内容的距离可能会被这种应用架构步骤延迟或者,从来都通不过。

终端用户80%的响应时间都花在前端(而非服务端处理)。而这其中绝大部分的时间又都花在下载所有的页面“组件”:图片、样式表文件、脚本文件、Flash等。这是黄金法则。而不是开始去进行对你系统架构的重新设计等困难的工作。最好先分散你的静态内容。这不仅会让响应时间大幅减少,而且也使得内容分发网络变得更加容易。

CDN是一组web服务器的集合,它跨越多个对用户更有效的内容分发位置。服务器对一个特别用户进行内容分发一般都是基于衡量网络的接近程度。例如,花费最少的网络带宽或者以最快的时间响应的服务器将会基于策略被选中。

某些大型的互联网公司都拥有其自身的CDN,但使用一个CDN服务提供商更具有经济效益。对于一个刚起步的公司或者一个死人站点, CDN服务的花费可能是难以接受的,但随着你的目标用户群的日趋庞大,甚至全球化,CDN对于加快响应将是必不可少的。在Yahoo,那些将静态内容移动到CDN上的web应用给终端用户提升了20%甚至更多的响应时间。选择CDN对代码的改动是轻量级的,并且那将能够动态地提高你站点的速度。

 

增加一个Expires或Cache-Control 头

下面是该规则的两个方面:

1、 对静态组件:实现“永不过期”策略(通过设置一个更久远的Expires头)

2、 对动态组件:使用Cache-Control响应头来帮助浏览器有条件地请求。

Web页面设计正在走向富客户端话,这意味着更多的脚本、样式文件、图片以及Flash将要放置在页面上。而第一次访问你页面的用户可能需要发出很多次的http请求,但使用Expires头可以使某些页面组件变得“可缓存”。这避免了对后续页面视图组件的不必要的请求。Expire头最通常被用于图片上,但他们应该被使用在所有的包括脚本、样式文件以及Flash组件中。

浏览器(以及代理)使用缓存来减少http请求的次数,使得网站页面加载得更快。web服务器使用Expires的http响应头来告诉客户端某个组件应该被缓存多久。这是一个更长时间的Expires头,告诉浏览器直到2010年4月15日该响应才会失效。

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


如果你的服务器是Apache,使用ExpiresDefault指令来设置一个相对当前日期的过期时间。下面的示例演示了使用ExpiresDefault来设置自请求时间起十天之后失效。

 ExpiresDefault "access plus 10 years"


记住,如果你使用一个时间更长的Expires头,那么无论何时你改变该组件的时候,你不得不改变该组件的名称。在Yahoo,我们在该步骤,通常采用如下办法——使用一个版本号嵌入到组件的名称中,例如:yahoo_2.0.6.js

使用一个过期时间更长的Expires头来影响页面视图,只是在用户已经访问过你的站点后才会起作用。所以当用户首次访问或者浏览器没有缓存的情况下,它对http请求数没有产生作用。因此该方法对性能的提升依赖于用户访问你的一个拥有初期缓存页面的频度(初期缓存已经包含了所有页面部件的缓存)。通过使用一个更长时间的Expires头,你可以增加被浏览器缓存组件的数量,并且可以重用后续页面视图而不需要完全请求整个页面。

Gzip 组件

在网络上传输http请求和响应的时间可以随着前端工程师的正确决定而显著减少。终端用户的带宽速度、Internet服务提供商、对等交换点的接近程度等等都超出了开发团队的控制,这是事实。但仍然有很多其他对响应时间产生影响的因素。而压缩通过减少http响应的大小来减少响应时间。

从HTTP1.1开始,web客户端通过在Accept-Encoding请求头来标识对压缩的支持。

 Accept-Encoding: gzip, deflate


如果Web服务器看到该请求头,它可能会使用客户端列出来的其中一种方法来压缩响应。而web服务器通知客户端的方式是在响应头中标注Content-Encoding:

 Content-Encoding: gzip


Gzip是当下最流行以及最有效的压缩方式。它由GNU项目组开发并且被标准化到RFC 1952。你可能会看到的另一种压缩格式deflate,但它缺失有效性,并且不是特别流行。

Gzip压缩通常将输出大小减小到原来的30%。现如今约90%的浏览器声称支持gzip压缩。如果你使用Apache,配置gzip的module取决于你的版本,apache1.3使用mod_gzip而apache 2.x使用 mod_deflate。

有可以代理或者浏览器期望的内容与压缩过的内容不匹配,成为一个众所周知的问题。幸运的是,随着旧版本浏览器的日渐淘汰,这些边缘问题也正逐渐减少。而Apache能够自动地帮助你加入Vary响应头。

服务器选择压缩哪些文件是基于文件类型的,但通常对此都有太多的限制。大部分的web站点都gzip它们的html文档。当然你的脚步以及样式文件也是值得压缩的,但许多web站点错失了这么做的机会。事实上,任何文本形式的响应,包括XML,JSON都是值得压缩的。而图片与PDF文件不应该被gzip,因为它们已经被压缩过了。尝试压缩它们不仅浪费CPU,而且反而会增加文件大小。

将样式表文件的引用放在头部

在Yahoo的网站中,通过研究我们发现,将样式表的引用放到文档的HEAD中能够使页面加载得更快。这是因为将样式表的引用放在HEAD中,允许页面逐步解析。

前端工程师很关系性能,他们希望页面被逐步地加载。我们希望浏览器任何时候都可以尽可能快地“打印”页面。这对那些拥有很多内容的页面以及那些网速很慢的用户来讲是特别重要的。给用户视觉反馈,比如进度指示,是非常重要的。在这种情况下HTML页面是一个进度指示器。当浏览器逐步地加载头部、导航条、头部的logo等。所有的这些都给正在等待页面的用户视觉反馈。这能够提供一个更好地用户体验。

将对样式表的引用放到html文档的底部,这会阻止页面在很多浏览器(包括IE)中的顺序呈现。这些浏览器会阻塞页面呈现来防止元素的样式变化带来的页面重绘。这样用户在浏览的时候将会看到一个空白的页面。

 

将脚本放置到底部

脚本产生的问题是,他们会阻塞并发的下载。HTTP1.1标准建议浏览器对每个主机页面部件的并发下载数不超过两个。如果你将你的图片放在多个不同的域名下,那么就可以获得同时不止两个的并发下载次数。然而,脚本在下载的时候,浏览器却不会开启任何其他的下载,甚至来自其他域名的也不会。

在某些情况下,将脚本移动到尾部并不是那么容易。例如,脚本使用document.write来插入页面内容的某个部分,那么它就不能被移动到页面的下面。这可能也是一个“域”的问题,当然有很多方式能够解决这些问题。

经常出现的一个替代的方案就是延迟脚本的执行。而defer属性表示脚本不包含document.write,它是一个线索——用来告诉浏览器,可以继续渲染。不幸的是,FireFox不支持defer属性。在IE中,脚本可能可以被“延迟”,但并不是尽可能地要求脚本这么做。如果一个脚本可以被延迟执行,那么它也可以被移动到页面的底部。那将能使你的页面加载地更快。

避免CSS表达式

CSS表达式很强大(但同时也很危险)。它能够动态地设置CSS属性。自从IE5开始被支持,但是在IE8中又开始被废弃。举个例子,背景色可以被设置为每个小时改变一次的CSS表达式:

background-color: expression( (new Date()).getHours()%2 ? "#B8D4FF" : "#F08A00" );


就像在这里展示的一样,expression方法接受一个javascript表达式。CSS属性被设置为Javascript表达式计算出的结果。Expression会被其他浏览器忽视,所以它只是在IE中需要设置一个持续变化的样式时才变得很有用。

而采用表达式的问题是,它计算的结果更多时间里都超过了人们的预期。因为它们不仅在页面呈现或者改变大小的时候被计算,甚至当页面被上下滚动或者用户的鼠标在页面上移动的时候也会计算。给CSS表达式加入一个计数器,将会允许我们跟踪什么时候或者怎样使得CSS开始了它的计算。在一个页面上移动鼠标可以很容易超过10000求值。

减少你CSS表达式求值次数的一种方式是使用一次表达式,表达式首次计算出结果的时候,将其设置到一个明确的值来替代CSS表达式。如果其在整个页面的生存期内必须动态地变化样式,使用事件处理器来取代CSS表达式是一个很不错的替代方案。如果你必须使用CSS表达式,记住他们可能会被计算成千上万次,因而可能影响到你页面的性能。

将Javascript与CSS搬到外部

很多关于提升性能的“准则”都在处理如何管理这些外部“组件”的问题。然而,在这之前,你需要先考虑一个最基本的问题:应该把Javascript与CSS包含到外部文件中吗,或者内联在页面本身?

总得来说,在现实中使用外部文件能够使页面加载地更快。这是因为javascript与CSS文件都被缓存在浏览器中。Javascript与CSS如果内嵌在HTML文档中,那么每次该文档被请求时,这些js与css都会被再次下载。这能够减少必要的http请求数,但却增大了文本本身的大小。在另一方面,如果javascript与css作为外部文件被缓存到浏览器或客户端,html文档的大小会减少同时也没有增多HTTP请求数。

一个重要的因素是那些外部的JS文件与CSS文件组件被缓存的频率也与HTML文档的请求数有关。对于这个因素,虽然难以量化,但可以使用各种各样的指标来衡量。如果你站点的用户在一个会话中可以查看多个页面视图并且你的页面中有很多重用的相同的脚本以及样式文件。这就能明显地体现从缓存外部文件中获得的收益。

许多网站在这样的指标中。对于这些网站而言,通常最好的解决方案是将Javascript与CSS文件作为外部文件。而对于这个原则,唯一例外的就是首页,你可以对它采用内联的方式。首页一般只有很少的(也许只有一个)页面视图,对一个会话而言,可能会发现内联javascript与CSS文件能够导致终端用户的响应时间变得更短。

通常对于许多页面的第一个视图,有许多技术能够减少HTTP请求,比如内联,或者通过分离到外部文件来从缓存获益。

减少DNS查找

Domain Name System(DNS)映射主机名到IP地址,就像电话薄映射人名到他们的电话号码一样。当你在浏览器地址栏中输入www.yahoo.com的时候,浏览器连接着的一个DNS解析器能够返回服务器的IP地址。DNS需要一定的时间,通常需要花费20-120毫秒的时间来对一个给定的主机名进行IP地址查找。除非DNS查找地址完成,否则浏览器不能从该主机下载任何内容。

DNS查找如果能够被缓存,可以获得更好的性能。这个缓存可以放在一个特殊的缓存服务器上,被用户的ISP或者本地区域网络来维护,但也有许多缓存发生在独立的用户计算机上。DNS信息被保存在操作系统的DNS缓存中(在Windows上是DNS客户服务)。大部分的浏览器都有他们自己的缓存,用以区别操作系统的缓存。一旦浏览器将DNS记录保存在它们自己的缓存中,它将不需要另外向操作系统请求记录。

IE默认缓存DNS查找30分钟,由DnsCacheTimeout注册表设置指定。Firefox缓存DNS查找一分钟,这是由network.dnsCacheExpiration配置设置的。

当客户端的DNS缓存为空(这里指浏览器和操作系统都为空)的时候,DNS查找的次数就等于网页上主机名唯一出现的次数。这其中就包含了页面URL、images、脚本文件、样式文件、Flash对象等,所使用的URL中的主机名。减少这些唯一URL主机名可以减少DNS的查找。

减少唯一主机名出现的次数能够潜在地减少发生在页面加载时的并发下载次数。避免DNS查找能够减少页面加载的时间,但减少并发加载的次数却有可能增加响应的时间。我的一个观点是,将这些组件分割为至少跨越两个但是最多不超过四个主机。这能够在两者之间取得平衡。

 

压缩Javascript与CSS

压缩指的是从代码中移除不必要的字符,来减少它的大小以加快响应。当所有不必要的东西(比如空白字符,空格,换行tab制表符等)被移除,文件就会变得很小。对于JS而言,这提高了响应时间,因为下载文件的大小小了很多。两个很流行的压缩JS的工具是JSMinYUI Compressor YUIcompressor也能够压缩CSS。

混淆是一种能够作用在源代码上的可替代的操作。它比压缩更为复杂,因而更容易产生错误的是混淆本身的步骤。对美国排名前十的网站的一项调查统计,压缩占有21%对比混淆占有25%。尽管混淆有一个更大的压缩比,但纯粹压缩的风险来得更小一些。

另外,压缩额外的脚本和样式,而内敛到文档中的<script>标签块以及<style>样式快也能够并且应该被压缩。甚至如果你gzip你的脚本和样式,它们的大小也能够减少5%或者更多。随着Javascript以及CSS使用和大小的增加,这将能够给你提供更好的性能。

 

避免重定向

重定向都是使用301和302状态码完成的。这里是一个301响应的http头部:

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


浏览器自动地引导用户到Location字段所标示的特定URL页面。重定向所需要的所有的信息都在该响应头中。而响应的实体通常都是空的。尽管他们有名称,301和302在实践中都不会被缓存,除非额外的响应标识头,比如Expires或者Cache-Control,来标识它们应该被缓存。而meta refresh标签与Javascript是将用户定向到其他页面的两种方式,但如果你必须做重定向,更好的技术实现是使用标准的3XX HTTP状态码,这主要是用来确保后退按钮能够正常工作。

最主要说明的一点事,重定向降低了用户体验。在用户与HTML文档插入一个重定向延迟了页面上所有的东西,因为页面没有什么可以呈现并且没有组建可以开始下载,知道HTML文档到达。

其中一个最没必要的重定向经常会发生,并且web开发者通常也不会意识到它。它发生在当一个URL结尾需要以一个‘/’结束,但如果缺失的话,就会发生。例如,访问http://astrology.yahoo.com/astrology导致了一个301响应,其中包含了一个对http://astrology.yahoo.com/astrology/的重定向(注意结尾加了)。在Apache中,通过使用alias或者mod_rewrite或者直接使用DirectorySlash就可以解决,如果你正在使用Apache处理器的话。

 

移除重复的脚本

在一个页面中引入相同的Javascript文件两次会损失性能。这不同于你想的那样。对美国排名前十的网站的一项统计发现,10个站点中有两个站点包含有重复的脚本。有两个主要的因素导致:团队规模以及脚本文件的数目。当发生这样的情况,重复的脚本文件引用会损失性能,因为,它们会创建不必要的HTTP请求并且会完成一些没必要的执行。

没必要的HTTP请求会发生在IE中,但不会发生在Firefox中。在IE中,如果一个外部的脚本被引入两次。并且没有缓存,它会在页面加载时产生两个HTTP请求。就算脚本被缓存,额外的HTTP请求也会在用户重新加载页面的时候发生。

除了生成的“不必要”的HTTP请求外,时间也会被浪费在执行这些脚本上。该冗余的JS执行在IE与Firefox中都会发生,与这些脚本是否被缓存无关。

避免不小心包含相同的脚本两次的一种方法是在你的模板系统中实现一个脚本关系模块。传统的包含脚本的一种方式是在你的HTML页面中使用SCRIPT标签。

<script type="text/javascript" src="menu_1.0.17.js"></script>


在PHP中,另一种替代方式是创建一个称之为insertScript的函数。

<?php insertScript("menu.js") ?>


除了防止相同的脚本被引入多次外,该函数可以处理脚本的其他问题,比如依赖性检查以及在脚本文件中加入版本号来支持Expires缓存头。

 

配置Etags

Entity tags(ETags)是一种供web服务器与浏览器使用,来决定在浏览器缓存中的组件是否匹配源服务器组件的方案。(“Entity”在这里等同于另一个单词“component”的含义:images,scripts,stylesheets等)。Etags被用来提供一种比last-modified 日期更为灵活的验证entities的方案。一个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.gif 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


ETag的问题在于,它们通常都是使用区别于宿主该站点的特殊服务器的唯一属性来构建。当一个浏览器从一个服务器上获得源组件并且稍后尝试在一个不同的服务器上来验证该组件时,ETag 是得不到匹配的。一个很通常的解决方案是采用一个服务器的集群来处理请求。默认的,无论是Apache还是IIS,它们嵌入数据到ETag都大大降低了对多服务器站点测试成功的可能性。

对Apache 1.3与2.X来讲,ETag的格式是inode-size-timestamp。尽管一个给定的文件可能位于跨多个服务器的相同的目录下,并且他们的大小相同,权限,创建时间等都相同。但,它的inode却因不同的服务器而已。

IIS 5.0与6.0对ETag有一个很简单的处理方案。ETag在IIS上的格式为Filetimestamp:ChangeNumber。ChangeNumber是一个用来跟踪对配置改变的计数器。所以,ChangeNumber在跨所有IIS服务器背后的网站上是不可能相同的。

所以导致的最终结果是,由Apache和IIS对基于相同的页面组件生成的ETag,在不同的服务器上是不相同的。如果ETag不匹配,用户就无法接受到一个为ETag设计的小而快的304响应。取而代之的是,它们将被响应以200状态码,同时伴随着所有组件内容作为响应。如果你只是将你的站点宿主在一个服务器上,那这将不会产生任何问题。但,如果你采用的是这种多服务器的部署方式,并且你采用的是Apache或者IIS的默认ETag配置,你的用户将会获得更慢的页面响应,你的服务器也会随之带来更高负荷的负载。你正在使用的更高的带宽以及那些代理,它们无法有效地缓存你的内容。甚至尽管你的组件有一个时间更长的Expires头,但任何时候,用户点击重新加载或者刷新按钮,仍然会向服务器发出请求。

如果你没有利用ETag提供的灵活的验证模型的优势,最好就干脆移除ETag。Last-Modified头的验证机制是基于组件的时间戳。ETag的移除减少了HTTP响应头部与后续的请求头部的大小。这篇Microsoft Support文章介绍了如何移除ETag。对Apache服务器而言,你只需要在你的Apache配置文件中,简单地加上下面这句:

 FileETag none


使Aajx可缓存

Ajax引入的其中一个好处就是,它能够对用户提供即时反馈,因为它从终端服务器异步请求信息。然而,使用Ajax也不能够保证用户将不再次按动它手里的鼠标来等待异步的javascript与xml响应的返回。在许多应用中,用户是否保持等待,取决于怎样使用Ajax。例如,在一个基于web的邮件客户端中,用户将等待Ajax的请求结果,因为他们需要找到所有符合他们搜索要求的邮件。你需要记住——异步并不意味着“瞬间”。

对于提高性能,优化Ajax响应是很重要的举措。提高Ajax性能的最重要的方式是使响应可缓存,就像在上面讨论的过期头和缓存头一样。

让我们来看一个例子。一个Web 2.0的邮件客户端,可能会采用Ajax来自动载入用户的地址簿。如果用户在距离上一次他使用该邮件客户端之后都没有修改过地址簿的话,那么可以从之前的响应中来读取地址簿(如果该响应预先被缓存过的情况下)。浏览器必须要记住,什么时候使用之前缓存过的地址簿,而什么时候引发一个新请求。可以在异步请求地址簿的URL上加入一个时间戳来标识用户修改他地址簿的最后时间,例如,&t=1190241612。如果自从上一次下载之后,地址簿没有被修改,时间戳将与之前的保持一致,这样就可以从浏览器的缓存中直接读取结果。而如果用户已经改变过它的地址簿,自从上一次读取之后。那么最新的时间戳将能够确保URL不与缓存的URL相匹配。

就算你的Ajax响应是动态创建的,并且只是针对单个用户的,他们也能够被缓存。这么做会让你的Web 2.0应用变得更快。

 

尽早地刷新缓冲区

当用户请求一个页面,在终端服务“组装”一个页面,无论如何最起码都需要200到500ms。在这段时间中,浏览器是闲置的,因为它在等待数据的到达。在PHP中,你有一个flush()函数。它允许你发送你部分已准备好的HTML响应到浏览器端,以在你的服务端还在忙于准备HTML页面其他部分的时候让浏览器能够开始下载页面的部件(js、css文件)。

放置flush()函数的一个好位置是在HEAD标签的后面,因为HTML的HEAD很容易生成,并且它允许你包含对CSS文件以及Javascript文件的引用。这样就是服务端仍然在处理,你也可以开始并行地下载这些页面部件。

例如:

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


对Ajax使用GET请求

Yahoo Mail团队发现:当使用XMLHttpRequest时,POST在浏览器中被作为两步来实现:首先发送头部,然后才发送数据。所以,我们最好使用GET请求,它只发送一次TCP包(除非你的cookies很大)。在IE中, URL的最大长度为2K,所以如果你发送超过2K的数据,就不能使用GET。

 

后加载组件

你可以自己看看你的页面,并且问你自己——为了呈现页面,哪些是绝对需要用来做初始化的?这样,剩余的内容与组件都可以等到必须的组件加载完之后再加载。

Javascript是分割在onload事件之前和之后加载的理想“侯选人”。例如,如果你有Javascript代码或者类库用来实现拖拽和动画效果,那它们就属于可等待的,因为在页面上“拖拽”元素发生在初始化呈现之后。可以找到的其他“候选”包括隐藏内容等。

当性能目标内联着其他web开发的最佳实践时也是很好的。在这种情况下,渐进增强的想法告诉我们,当浏览器支持Javascript时,这能够提高用户体验,但你不得不确信,当Javascript无法执行时,页面也能够正常工作。所以,在你已经能够确信页面工作地很好以后,你可以加强一些后期加载,给你更多的性能提升,比如拖放动画等。

 

预加载组件

预加载可能看起来和后加载相反,但它最终确实有一个不同的目标。通过预加载组件,你可以利用浏览器正空闲的时间优势,来请求你将要使用的部件(比如图片、样式文件或脚本文件)。采用这种方式,当用户访问下一个页面,你可能已经下载完大部分的页面部件并缓存在了客户端,这样你的页面将会加载地更快。

其实,这里有好几种预加载的方式:

无条件预加载—一旦开始加载,你就去加载某些额外的组件(也许暂时你还不需要用到)。查看一下google.com,可以作为一个示例,看看一个精灵图片是如何被请求加载的。该精灵图片其实在google.com的主页并不需要,但最终肯定会在展示搜索结果的页面中使用。

有条件预加载—基于用户的操作,你做出一些有根据的假设。在search.yahoo.com,当你在输入框中开始输入的时候,你将看到一些额外的组件是怎样被加载的。

有预计的预加载—在部署一个全新的设计之前可以利用预加载的优势。这经常发生在一个新版本发布之后,你可能会听到——新站点是如此的酷,但它却比之前的老版本慢得多。其中部分原因是,用户访问你的老版本是在一个全缓存的环境下,但新版本完全没有经过缓存。你可以在老的站点中,预计性地在浏览器空闲的情况下加载一些新站点中需要的图片或脚本文件。

减少DOM元素的个数

一个复杂的页面意味着要下载更多的数据,也意味着Javascript对DOM的访问会更慢。如果你在页面循环500或者5000个DOM元素,当你给它们加入事件处理器时,它们确实会产生不同。

很多DOM元素可能会产生一些问题——有些应该提升页面的标记而不是必要地删除内容。你是否使用嵌套的table来布局?你是否为了解决布局上的问题,向页面上堆积更多的<div>?很可能有更好地或者在语义上来讲更正确的方式来处理你的标签。

YUI CSS ulities对于你的布局能够提供很大的帮助:grid.css能够帮助你均衡布局,fonts.css与reset.css帮你从浏览器特性的苦海中解脱出来。这是一次重新开始思考布局的机会,例如使用<div>只是在当语义上确实需要使用的时候,而不是因为它能够呈现一个新行。

DOM元素的数目很容易测试,只需要在Firebug的命令行中敲入:

Document.getElementByTagName(‘’).length

但,多少DOM元素才算太多呢?查看其他的一些简单页面,他们有好的标签结构。例如:Yahoo! Home Page是一个相当“重量级” 的页面,但仍然在七百个元素之内(HTML 标签)。

 

跨域分割组件

分割组件允许最大化地并发下载。确信你同时使用不超过24个域。例如,你可能将你的HTML文档以及动态内容持有在www.example.org上,然后可以将静态内容分割到static1.example.org和static2.example.org上。

想获得更多信息,请查看MaximizingParallel Downloads in the Carpool Lane

 

最小化iframes的数目

Iframe允许一个HTML文档被插入到父文档中。理解iframe如何工作很重要,因为这有助于你有效地使用它:

<iframe>优点:

l  帮助放慢第三方内容比如徽章和广告

l  安全沙盒

l  并发下载脚本

 

<iframe>劣势:

l  即便是空白页面也很“昂贵”

l  阻塞页面的加载

l  没有语义

 

没有404

HTTP 请求很“昂贵”,所以发起一个HTTP请求并获得一个没有用的响应(例如404 Not Found)总是没有必要的,并且将拖慢用户的响应时间却没有任何好处。

某些站点有帮助性的404——你想XXX?这很好地提高了用户体验,但也浪费了服务端资源(比如数据库等)。特别糟糕的是,当一个链接到外部Javascript的链接是错误的并且结果导致了404。首先,该下载将阻塞并行的其他下载,接着如果他是javascript文档,浏览器可能会尝试转换404响应体,来尝试发现一些有用的内容。

 

减少Cookie的大小

HTTP Cookies 通常用于验证,例如权限和个性化。c在往返于web服务器和浏览器的HTTP 头中,ookies的信息会被交换。保持cookies尽量小很重要,因为它影响用户的响应时间。

想获得更多的信息,请访问When theCookie Crumbles。记住这些结论:

l  限制不必要的cookies

l  保持cookies尽量小很重要,因为它影响用户的响应时间

l  要注意,在适当的域级别上设置Cookie,而不对其他子域产生影响

l  设置一个适当的过期日期。一个较早的过期时间或者在不久之后删除cookie将会提高用户的响应时间。

 

为页面组件使用无Cookie的域名

当浏览器对静态文件发送一次请求的同时也携带了cookies,而对服务端而言,这些cookies是无用的。所以他们只是增加了网络流量。你应该确保将静态组件作为无cookie的请求发送。创建一个子域并且将你所有的静态组件放在子域里面。

如果你的域名为www.example.org,你可以将你的静态组件放在static.example.org上。然而,如果如果你已经在相对于www.example.org来讲的顶级域:example.org上设置了cookies,那么所有static.example.org上将包含那些cookies。在这种情况下,你可以购买一个完整的新域名,来放置你的静态文件,并使该域名无cookie。Yahoo!使用的是yimg.com,YouTube使用ytimg.com,Amazon使用images-amazon.com等。

在一个无cookie的域名上放置静态组件的另一个好处是,有些代理可能会拒绝缓存那些带有cookies的组件。如果你想,你应该对你的主页使用example.org或者www.example.org。省略www让你别无选择,只能将cookies写到
.example.org,所以,从性能的角度出发,最好使用www.subdomain,将cookie写到该子域上。

 

最少化DOM访问

使用JS访问DOM元素很慢,所以为了让页面有一个更为快速的响应,你应该:

l  取消对已访问元素的引用

l  更新“离线”的节点,然后将他们加入树中

l  避免用JS处理布局问题

想获得更多的信息,请阅读 "High Performance Ajax Applications"

 

开发灵巧的事件处理器

有时页面响应不够迅速,是因为太多的事件处理器挂接在不同的DOM树的元素上,他们会很频繁地执行。这就是为什么使用事件代理是一个很好的解决方案。如果你有10个按钮在一个div中,附加仅仅一个事件处理器给div包装器,而不是为每个按钮都制定一个处理器。事件冒泡机制将能够使你捕获到事件以及它起源于哪个按钮。

为了在DOM树完成的时候做某些事情,你也不需要等待onload事件。经常,所有你需要的指示你想访问的元素在DOM树结构中可以被访问。你不需要等待所有的图片被下载完成。DOMContentLoaded是一个你可能值得考虑用来替代onload的事件,但知道它在所有浏览器中可采用之前,你可以使用YUI Event utility,它有一个onAvilable函数。

为了获得更多的信息,你可以阅读这篇文章——"HighPerformance Ajax Applications"

 

选择<link>而不是@import

之前的最佳实践中,其中有一条是——CSS应该在文档的头部,这样可以实现逐步呈现。

在IE中@import和在页面的底部使用<link>的行为相同。所以,最好不要使用它。

 

避免筛选器

IE专有的AlphaImageLoader筛选器是用来修复在IE版本小于7的浏览器中半透明的PNG真彩色问题。该问题使用这个筛选器,当图片正在被下载的时候,可以用来阻塞图片的呈现并释放浏览器。它也增加了内存的开销并且被应用到每个元素上面,不是每个图片,是每个元素。所以,该问题是多方面的。

最好的方案是完全避免对AlphaImageLoader的使用。用PNG8取而代之,他们在IE中表现地很好。如果你一定要用AlphaImageLoader,使用带下划线的hack手段——_filter,这就避免了该方法对你的IE7+用户的“惩罚”。

 

优化图片

在前端设计师为你的页面创建完图片之后,在你向你的web服务器请求这些图片的时候

,这里仍然有些事情值得你尝试。

l  你可以查看GIF图片,并且看是否他们用的是调色板的大小对应图像中的颜色数量。使用Imagemagick,可以很容易地查看使用identify–verbose image.gif

当你看到一个图片使用四个颜色以及一个250位颜色的“插槽”在调色板中,那就表示还有提升的空间。

l  尝试将GIF图片格式转为PNG格式,看是否大小有所减小。大部分情况下,答案都是肯定的。开发者通常都很不情愿使用PNG格式的图片(由于浏览器支持受限的问题),但这在现在来讲已经是一个过去时了。唯一真正的问题是真彩色的PNG中的Alpha透明度问题,但同样如此,GIF也不是真彩色,并且也不支持变量的透明度。所以任何一个GIF能够做到的,一个调色板PNG(PNG8)也能够做到(除了动画)。该简单的imagemagick命令会完成对一个GIF图片到PNG格式图片的“安全”转换:

Convertimage.gif image.png

“所有我想说的就是——给PNG一次机会”

l  为你所有的PNG图片运行pngcrush(或者任何其他PNG优化工具)。例如:

Pngcrushimage.png –rem alla –reduce –brute result.pgn

l  给你的JPEG图片运行jpegtran。该工具能够对JPEG完成无损优化,例如旋转等,也能够用来优化以及移除内容或者其他没有用的信息(例如EXIF 信息)。

 

优化CSS精灵

l  在精灵中水平地呈现图片,而不是垂直地呈现,将能够使文件更小。

l  在精灵中合并相似的颜色可以使得对颜色计算的消耗降得更低,例如256位的颜色就很适合PNG8。

l  “变得对移动设备友好”并且不要在精灵中产生很大的差距。这不会体现在文件的大小上,但这能够使得需要的内存减少掉为用户代理来比较图片像素的开销。100X100的图片是1万个像素,而1000X1000就是1百万个像素。

 

不要在HTML中延展图片

不要使用一个比你需要的尺寸更大的图片,仅仅因为你觉得你可以在HTML中设置宽度和高度。如果你需要

<img width=”100” height=”100” src=”mycat.jpg”alt=”My Cat” />

然后你的图片就应该是差不多100100而不是一个延展到500500大小的图片。

 

确保favicon.ico很小并且可缓存

Favicon.ico是一个放置在你服务器根目录下的一个图片。它是必须的,因为如果没有它,浏览器将会一直请求它。所以,最好不要响应以一个404 Not Found。同时,既然在相同的服务器上,在每次请求它的时候cookies都会发送。该图片也会干扰下载顺序,例如在IE中,当你在加载的时候请求一个额外的组件,favicon将会再这些额外的组件之前被加载。

因此,尽量减少favicon.ico带来的弊端:

l  确保它很小,最好小于1K

l  设置一个你觉得合适的Expires头。你可以安全地设置Expires头在未来的几个月过期。



保持组件在25K以下

该组件的限制是由于,有这样一个事实——iPhone不会缓存超过25K的组件。注意,这是指未压缩的大小。这样只采用gzip压缩可能不是很有效。

 

打包组件到一个文档中

打包组件到一个文档中就像一个带有附件的email一样,它帮助你在一个http请求中获取几个组件(记住,HTTP请求是很昂贵的)。当你使用此技术之前,首先核查一下用户代理是否支持它(iPhone 不支持)。

 

避免错误的图片引用

Image标签的空src属性将带来不可预期的行为。它有两种表现形式:

1、  纯粹的HTML:

<imgsrc=””>

2、  Javascript

Var img =newImage();

Img.src=””;

 

这两种形式都会有相同的影响:浏览器将向你的服务器发送另一个请求。

l Internet Explorer:向当前页面所属的文件夹发送一个请求

l Safari 以及 Chrome:向最终页面本身

l FireFox 3以及早期版本的行为和Safari 以及 Chrome行为一致,但3.5版本解决了该问题【bug 444931】并且不再发送请求

l Opera 不做任何事情

 

为什么该做法很不好?

1、  通过发送大量不可预期的通信来削弱你服务器的性能,特别是那些每天被访问几百万此的页面更是这样。

2、  浪费服务器的计算周期来生成一个将不会被显示的页面。

3、  可能破坏数据。如果你跟踪请求的状态,要么通过cookies要么以另一种方式,你将有可能看到破空数据。尽管图片请求不返回一个图片,但所有的响应头被读取并且被浏览器接受,包含所有的cookies。虽然响应的其余部分被废弃,但损害可能已经发生。

导致该行为的问题是,在浏览器中解析URI的方式不尽相同。该行为被定义在RFC 3986-统一资源标识。当一个空字符串被当做一个URI,它被认为是一个相对URI,并且根据定义在5.2节的算法被解析。该特殊的例子——一个空字符串,被列在5.4节。

Firefox, Safari, and Chrome都能够按照标准正确地解析空字符串,但IE并没有正确地解析,而是选择了遵循规范的早期版本——RFC 2396(它已经过时,并被3986替代)。所以,从技术角度来讲,浏览器将以它们自身支持的方式来解析相对URI。该问题在这个上下文中,主要想说明空字符串是无意义的。

HTML 5增加了src属性的描述,指示浏览器不作出第4.8.2节中的额外要求:

Src属性必须被标识,必须包含一个验证过的URL并引用一个非交互的可选动画、既不是页面也不是脚本的图片资源。如果元素基本的URI与文档的路径相同,这样src属性的值必须为非空的字符串。

值得期待的是,浏览器将不会存在这个问题。可能仍然需要一段时间来适应确保浏览器不小心执行了该行为。