【51CTO译文精选】困扰许多网站所有者很久的一个问题是网站访问速度总是那么慢。想购买独立带宽,预算不允许,想购买CDN加速,价格又太贵。那有没有经济实惠的解决办法呢?从目前的大环境来分析,我们也只有通过技术手段来解决这个问题了。
对Web开发有着深刻理解的著名搜索引擎厂商Google前段时间发布了一款Firefox /Firebug插件Page Speed(为什么是Firefox 而不是Chrome?),并已开源,网络管理员和Web开发人员使用这个插件对Web页面的性能进行评估,并提供改善性能的有益建议。并于今日呼吁大家共同努力,一起为互联网加速,可不是提什么空口号,Google已经将其研究结果和数据共享了出来,并提供了大量的网站加速教程,本文就对这些教程加以整理,与诸位共勉。(近来,Google对网站加速的重视程度越来越高,详情见51CTO之前的头条谷歌启动Web加速计划 拟改进TCP/IP和HTTP协议,以及Google研究小组有关搜索速度的最新报告)
css:每个声明只使用一次
要想让你的网站速度更快,其中重要的一条就是发送到浏览器的客户端代码要尽可能小,在优化css文件时,一个重要的原则就是每个声明只使用一次。这样就可以严格使用选择器组合。
例如,你可以将这两个规则
- h1 { color: black; }
- p { color: black;
合并成一个规则
- h1, p { color: black; }
这个例子是最简单的了,实际开发中的情况要复杂得多,根据我个人的开发经验,每个声明只使用一次后可以将css文件的大小降低20%-24%。下面再看一个复杂一点的例子:
- h1, h2, h3 { font-weight: normal; }
- a strong { font-weight: normal !important; }
- strong { font-style: italic; font-weight: normal; }
- #nav { font-style: italic; }
- .note { font-style: italic; }
根据每个声明只使用一次的原则,将其缩减为
- h1, h2, h3, strong { font-weight: normal; }
- a strong { font-weight: normal !important; }
- strong, #nav, .note { font-style: italic; }
注意!important声明可以与众不同。
在应用这个方法时有几点需要牢记于心:
◆首先,太长的选择器可能致使这个方法无效,重复选择器如html body table tbody tr td p span.example不能减少文件尺寸大小,实际上,每个声明只使用一次可能意味着选择器的数量会上升,甚至可能导致css文件更大,使用紧凑的选择器是更有帮助的,并且可以提升样式表的可读性。
◆第二,小心css规则,当一个用户代理不能解析选择器时,必须忽略声明,当你遇到这种情况时,只需要修改一下规则,让其可以使用多次即可。
◆第三,也是最重要的一点,如果你对样式表进行了排序,这个原则会改变你的排序,但这个顺序可以由浏览器来决定,如果你使用这种方法运行出了问题,最好的解决办法就是产生一个异常,并在出现问题的地方让声明使用多次就可以了。
在使用一个声明只使用一次的方法维护样式表时要格外小心,可以通过优秀的编辑器来跟踪变化(如显示变化行),然后在需要的地方插入声明,这种做法需要合并到工作流中。其实一个有效的方法是使用缩进排版,这样就很容易发现重复的声明的了。
使用gzip压缩页面
Gzip压缩需要Web服务器的支持,在没有gzip压缩的情况下,Web服务器直接把html页面发送给浏览器,而支持gzip的Web服务器将把html压缩后再发给浏览器,支持gzip的浏览器再在本地进行解压和解码,并显示出来。Gizp会将css文件,js文件,html文件中那些重复的字符串、标签、空格和样式定义进行压缩。我们还是举一个例子加以说明。
下面的代码未压缩前69字节,压缩后85字节。
- < h1>One< /h1>
- < h2>Two< /h2>
- < h3>Three< /h3>
- < h4>Four< /h4>
- < h5>Five< /h5>
我们将标题标签替换成div标签,源代码大小增加了10字节,变成了79字节,但经过压缩后只有66字节。
- < div>One< /div>
- < div>Two< /div>
- < div>Three< /div>
- < div>Four< /div>
- < div>Five< /div>
HTTP缓存
如果你正确设置了HTTP缓存报头,不但Web页面载入速度更快了,而且Web服务器的负荷也减少了,是一个双赢的举措。
缓存也就是资源的本地副本,因为很多资源都不会频繁更新,这时浏览器就可以直接从本地获取资源,不用到服务器去取,节省了连接时间和资源下载时间,使用HTTP缓存真正起关键作用的是HTTP缓存报头,这个报头指定了资源的有效时间和最后修改时间。
http协议有两种方法定义资源的有效时间:Expires和Cache-Control: max-age报头。Expires报头指定资源的过期日期,而max-age指定的是当资源下载到本地后多长时间有效。实际上它们要表达的最终含义是一致的。建议将资源的有效日期指定为1个月,长一点的话可以指定为1年。这两个报头一般只需要设置一个即可,如果同时进行了设置,那么max-age的优先级比Expires要高一些。如果你的资源比这个设定期限更新得要频繁一些,那么你可以采取对资源重新命名的方法,通常的做法是在URL中给资源加上版本号,当然这是引用资源的html页面也得做对应的修改。
当本地资源是有效状态时,浏览器有两个选择,其一是全部重新下载所有资源,其二是仅下载有变化的资源,为了让浏览器只下载有变化的资源,我们需要在GET请求中加上条件,http协议给了我们两个选项:Last-Modified和ETag报头。
Last-Modified报头指资源的最后修改时间,ETag报头是每个资源不同版本的唯一身份识别号,我们推荐你使用Last-Modified报头,因为日期如果足够早,浏览器可以跳过请求全部资源的。下面是两个测试页。
在第二次载入页面时,可以再次点击链接,或者在地址栏中再按一次回车,如果点击刷新按钮,如果资源还在缓存中的话,会强制浏览器重新使用附条件的GET请求去获取资源。
为了更清晰地看到这个完整过程,可以使用HttpWatch工具,它可以查看http报头。如果使用Apache的话,可以参考mod_expires模块的资料。
#p#
使用Page Speed提升网站性能
Page Speed是由Google发布了一款开源Firefox 插件,它可以评估Web页面的性能问题,并能够给出合理的优化建议,同时它也可以将资源变得更小。
◆图片压缩
图片文件中通常都包含了一些额外的信息,如JPEG图片包括创建图片所用工具的信息,PNG图片通过创新编码可以缩小文件大小,这些转换都是无损的,压缩后的图片和未压缩的图片看起都是一样的,但占用的字节数却少了,
Page Speed使用优化图像功能尝试为页面中的所有图片进行无损压缩,如果成功的话,就显示压缩后的版本,为了使用图片的最低版本,在Page Speed面板中点击链接到压缩版本,保存,使用它代替原始图片。
◆削减JavaScript
移除大型JavaScript文件中的注释和空格可以大幅度减少文件的尺寸,但功能却无丝毫损伤。Page Speed提供了削减JavaScript的功能,削减成功后Page Speed会显示一个到该文件的链接,如果要使用削减后的文件,点击那个链接,保存文件,然后到html页面中重新引用这个更小的JavaScript文件。
但这样做有一个问题,削减后的JavaScript文件的移动性就降低了,如果你经常更新的话,建议你使用另一个削减命令行工具JSMIN。
◆移除不用的css
移除css文件没有使用到的规则将有助于减少css文件的大小,因为浏览器在浏览Web页面时往往会将css文件下载到本地,这样就可以减少发送到浏览器的信息量,从而加速页面反应速度,但值得注意的是,有些外部css文件是由多个Web页面引用的,在移除时要特别小心。
Page Speed提供了移除不同的css功能,它可以告诉你那些css规则是没有被使用的。
除了上面提到的这三个功能外,Page Speed还有更多的功能等待你去发掘。
减少浏览器reflow
Reflow是浏览器为重新渲染文档或文档中的一部分,重新计算文档元素的位置和几何大小的统称,因为reflow在浏览器中是一个用户拦截操作,这对于开发人员理解如何提升reflow时间和不同文档属性(DOM深度、css规则效率、不同类型的样式变化)对reflow时间的影响。有时reflow一个元素可能需要reflow文档中的所有元素,以及跟着它的所有元素。
有很多中用户行为和DHTML变化可以触发reflow,如改变窗口大小、使用包含计算样式的JavaScript函数、从DOM中添加或移除元素以及改变某个元素的类别都可以触发reflow,值得注意的是有些行为可能导致reflow的时间比你想象中的要多。来看一张图。
从上图可以看出不是JavaScript中任何样式的变化都会触发reflow,还可以看出浏览器版本越高reflow时间更好了。
在Google,我们对我们的页面进行了大量的测试,最终我们认为要往页面中添加UI元素时最值得考虑的因素就是reflow时间了。下面是我们整理的可以帮助减少reflow时间的方法:
◆减少不必要的DOM深度,在DOM树上任意一层发生的变化都会影响到DOM树的所有层,当然这在reflow时花的时间肯定比较多了。
◆缩小css规则,移除不用的css规则。
◆如果你使用更复杂的演示变化,如动画,那么你最好使用绝对位置或将位置固定下来。
◆避免不必要的复杂的css选择器,特别是衍生选择器,它需要更多的CPU时间处理选择器匹配。
#p#
优化JavaScript代码
客户端代码可以让你的应用程序动起来变得更有活力,但这些代码可能使浏览器变得更低效,不同的客户端性能也不一样,这里我们讨论几个优化JavaScript的技巧。
◆字符串的使用
在IE6和IE7中因字符串级联导致的主要问题是垃圾回收性能,虽然这些问题在IE8中已经得到解决,但如果你的用户大部分仍然在使用IE或IE7,你就得格外注意这个问题了。看一个例子先:
- var veryLongMessage =
- 'This is a long string that due to our strict line length limit of' +
- maxCharsPerLine +
- ' characters per line must be wrapped. ' +
- percentWhoDislike +
- '% of engineers dislike this rule. The line length limit is for ' +
- ' style purposes, but we don't want it to have a performance impact.' +
- ' So the question is how should we do the wrapping?';
◆使用连接代替级联:
- var veryLongMessage =
- ['This is a long string that due to our strict line length limit of',
- maxCharsPerLine,
- ' characters per line must be wrapped. ',
- percentWhoDislike,
- '% of engineers dislike this rule. The line length limit is for ',
- ' style purposes, but we don't want it to have a performance impact.',
- ' So the question is how should we do the wrapping?'
- ].join();
与此类似,在条件语句中使用级别也是很低效的,错误的做法:
- var fibonacciStr = 'First 20 Fibonacci Numbers
- ';
- for (var i = 0; i < 20; i++) {
- fibonacciStr += i + ' = ' + fibonacci(i) + '
- ';
- }
正确的方法:
- var strBuilder = ['First 20 fibonacci numbers:'];
- for (var i = 0; i < 20; i++) {
- strBuilder.push(i, ' = ', fibonacci(i));
- }
- var fibonacciStr = strBuilder.join('');
◆定义类函数
下面的函数是低效的,因为每次构造baz.Bar的实例时,会创建一个新的函数和闭包:
- baz.Bar = function() {
- // constructor body
- this.foo = function() {
- // method body
- };
- }
正确的方法是:
- baz.Bar = function() {
- // constructor body
- };
- baz.Bar.prototype.foo = function() {
- // method body
- };
使用这个方法时,无论baz.Bar构造了多少个实例,都只会为foo创建一个函数,而且不会创建闭包。
◆初始化实例变量
使用实例变量值类型初始值初始化实例变量声明,如数值、布尔值、空值、未定义或字符串,这样可以避免每次调用构造器时运行不必要的初始化代码。还是来看一个例子:
- foo.Bar = function() {
- this.prop1_ = 4;
- this.prop2_ = true;
- this.prop3_ = [];
- this.prop4_ = 'blah';
- };
可以使用:
- foo.Bar = function() {
- this.prop3_ = [];
- };
- foo.Bar.prototype.prop1_ = 4;
- foo.Bar.prototype.prop2_ = true;
- foo.Bar.prototype.prop4_ = 'blah';
进行代替。
◆避免使用with
避免在代码中使用with,它会影响到性能,因为它修改了范围链,因此需要花费更多时间到其它范围去查找。
◆避免浏览器内存泄漏
对于Web应用程序而言,内存泄漏是很常见的事情,可能因此导致巨大的性能问题。因为浏览器的内存使用在增长,你的Web应用程序和其它程序就会变慢,最常见的内存泄漏是在JavaScript引擎和浏览器C++对象实现的DOM之间形成了循环引用,如在JavaScript引擎和IE浏览器的COM基础结构之间,以及JavaScript引擎和Firefox XPCOM基础结构之间。
#p#
优化网页图片
当你在优化网页代码时,别忘了那些静态内容,包括图片,经过简单的优化就可以大大减少下载大小,并且也不会降低网站的质量。
我们总结了一些技巧,可以帮助你加快网页图片的载入速度:
◆剪裁掉多余的空白
如果确实需要空白内边距,可以使用css代码来实现。
◆使用最佳的文件格式
可以通过一些专业的图片处理工具,如photoshop就可以直接保存为网页图片,如果将图片颜色从256色调整到32色,那文件大小会明显变小,下面来看看一张图片不同文件格式时的大小对比。
上面这张图片不同文件格式时的大小对比结果如下:
◇JPG, 60 quality - 32K
◇PNG-8, 256 colors - 37K
◇GIF, 256 colors - 42K
◇PNG-24 - 146K
由于8位png和gif格式相对而言图像质量不错,文件大小更小,因此在网页中建议多使用这两种格式。
PHP性能技巧
51CTO编辑注:对于以下这部分内容,PHP小组已发布公开信反驳,其中的有些建议究竟是否正确还有待进一步研究。
由于PHP的使用量非常大,所以有必要放在这里说一说。首先有一点需要阐明的是,网站的性能与PHP版本,Web服务器环境和代码的复杂度都是有关的。
◆升级PHP版本
首先要做的是升级PHP版本,如果你还在使用PHP3或PHP4,那赶快升级吧!
◆使用缓存
使用缓存模式,如memcached,可以将数据库查询结果或网页缓存起来,加快Web服务器的响应速度。
◆使用缓冲输出
PHP使用内存缓冲存储你的脚步尝试打印的所有数据,这个缓冲看起来好像让你的网页变慢,因为用户必须等待缓冲被填满,幸运的是,你可以做一些修改强制让PHP快速清空输出缓冲。
◆没有原因尽量不要复制变量
有些PHP新手试图通过拷贝预先定义的变量使代码变得清晰,但事实是这样会导致双倍的内存消耗。我们来看一个例子,假设有个不怀好意的用户向文本区域插入了512KB的内容,那么会使用1MB内存:
- $description = strip_tags($_POST['description']);
- echo $description;
上面这个例子就是没有原因的拷贝变量,其实可以简化它,避免额外的内存消耗:
- echo strip_tags($_POST['description']);
◆避免在循环中执行SQL查询
一个常犯的错误是在循环中使用SQL查询,这样会引起多次数据库往返,降低脚本速度,在下面的例子中,你可以修改循环构建一个SQL查询,一次性插入所有的用户:
- foreach ($userList as $user) {
- $query = 'INSERT INTO users (first_name,last_name) VALUES("' . $user['first_name'] . '", "' . $user['last_name'] . '")';
- mysql_query($query);
- }
改为:
- INSERT INTO users (first_name,last_name) VALUES("John", "Doe")
不用循环,你可以合并成一次数据库查询:
- $userData = [];
- foreach ($userList as $user) {
- $userData[] = '("' . $user['first_name'] . '", "' . $user['last_name'] . '")';
- }
- $query = 'INSERT INTO users (first_name,last_name) VALUES' . implode(',', $iserData);
- mysql_query($query);Produces:
- INSERT INTO users (first_name,last_name) VALUES("John", "Doe"),("Jane", "Doe")...
◆为长字符串使用单引号
PHP允许对字符串变量封装同时使用单引号和双引号,但是是有区别的,使用双引号时,告诉PHP引擎读取字符串内容并查找变量,然后使用它们的值替换它们,对于一个很长的但没有包含变量的字符串,将导致性能下降。
◆使用switch/case替代if/else
使用switch/case比使用if/else性能更好,易读性和易维护性也更佳。
#p#
预取资源
预取资源就是当用户还没有请求该资源就预先下载到本地,那当用户真正请求时,可以大大减小网络延迟时间,甚至消除网络延迟时间。
对于交互式网站,优化速度不仅仅是减小初始下载大小,当用户交互时可以下载额外的资源,这些行为的速度依赖于下载这些资源的时间,将下载资源变小可以加快网站速度。在设计预取资源时要考虑以下几个问题:
◆研究用户行为
如果你的用户在某个特定的页面或行为上花费了大量的时间,那这就是你该优化的,你可以通过查找服务器日志来找出这些页面和行为。对于picasa网络相册,我们知道最多的操作是一张张浏览图片,那么这就是非常合适的预取对象。
◆衡量网页准好的时间
在当前页面没有显示完时不应该提取新页面的数据,对于动态页面,你需要为页面上每个外部文件添加JavaScript载入处理程序,当这些资源全部下载下来后,为用户下一个行为预取数据就非常合理了。
◆使用工具测试
在正式修改代码前,先用Page Speed这类工具进行测量,要避免一次提取太多数据造成网站变慢。
适当的包含样式和脚本
网页载入时间一般80-90%的时间都花在等待网络上了,最有力的方法是消除序列化下载资源。
联合外部JavaScript文件
下载外部脚本文件时一般浏览器会等它下载完了再继续后面的下载,这与并行下载图片的行为是完全相反的。
例1:两个外部脚本文件
例2:两个外部图片
在例2中,两个需要1秒的图片合在一起时,总共也只需要1秒就完成下载了,在例1中,两个需要1秒的脚步合在一起时,却需要2秒才能完成下载,这是两个不同的并行和串行下载。因此,如果你有多个外部脚本文件,最好将它们合并到一个文件中。
在外部JavaScript文件前包含额外的css文件
由于脚本文件会阻止后面的下载,那些已经在下载中的将会继续下载。
例3:在css之前的脚本
例4:在脚本前的css
在例3中,脚本在css文件前会导致浏览器堵塞,要花2秒时间,但只需要将css移到脚本前面就可以让下载过程并行执行,如例4。因此,如果你既有外部css文件,又有外部脚本文件,那么记住一定要将css文件放到脚本文件的前面。
在外部css和其它资源之间不包含内联JavaScript
使用内联脚本标签时,即使它没有下载任何东西,也会阻碍后面的下载。
例5:css后的内联脚本
例6:css前的内联脚本
例5中内联脚本块位于样式和脚本之间,下载时是串行的,在例6中,移到样式前面后,再一次恢复到并行下载模式。因此,如果你有一个外部css文件,不要在你的css文件和下一个下载的资源之间插入内联脚本标签。
#p#
减小HTML文档的文件大小
想要减少网站的载入时间最佳的办法就是减小HTML文件的大小,有很多种办法可以实施,首先我们看看html哪些标签是可以瘦身的。
◆HTML 4
根据HTML 4 DTD标准,可以有很多标记都省略掉,下面以斜体表示的标记都可省略掉:
< /area>
< /base>
< body>
< /body>
< /br>
< /col>
< /colgroup>
< /dd>
< /dt>
< head>
< /head>
< /hr>
< html>
< /html>
< /img>
< /input>
< /li>
< /link>
< /meta>
< /option>
< /p>
< /param>
< tbody>
< /tbody>
< /td>
< /tfoot>
< /th>
< /thead>
< /tr>
例如,如果你有标记为< li>List item< /li>的列表项目,那么你可以将其省略为< li>List item,在表示段落结束时一般使用< /p>标记,但这下也可以使用< p>My paragraph了,这种省略语法对html、body、head都有效,在html中它们不是必须的。这样一通省略后,往往可以使整个html文件大小减小5-20%。
◆HTML 5
虽然HTML 5仍然在开发阶段,它向你提供了更多减小文件大小的选项。例如。HTML 5允许你象下面这样设置文档类型:
- < !DOCTYPE html>
相对
- < !DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
你会注意到HTML 5 DTD更短了。当你指定文档的编码时,HTML 5更易于使用,并且更轻巧了,< meta charset="utf-8">和< meta http-equiv="content-type" content="text/html; charset=utf-8">表示的含义是一样的,浏览器一看也是知道要处理HTML。
在HTML 5中另一个值得一提的是可以省略掉与MIME类型关联的类型属性了,这些属性如type="text/css"或type="text/javascript"描述了内容的类型,现在可以使用< script>代替< script type="text/javascript">,使用< style>代替< style type="text/css">。
如果你将上面这些方法集合起来使用,将会节约10-20%的文件大小。请浏览Google的隐私页面,它就全部采用了这些方法。
UI消息和感觉等待时间
对于很多用户而言,速度并不等于性能,用户对一个网站的速度感觉会受到他们的总体体验影响,包括他们是否能够有效地获取到想要的内容,以及系统是如何人响应的。
在设计你的网站时,要考虑用户访问该站点的目的是什么,越快(越容易)完成访问网站越好,如果用户在获得内容时遇到了很多困难,他们在完成任务后肯定会马上离开你的站点,并可能再也不愿意回来。
其实要让用户觉得网站使用起来感觉很舒服,你有很多事情要做,这里只讨论3个主题。
1. 网站设计是否足够简单,即使是第一次使用该网站的用户也能够顺利地找到他们想要的内容。要达到开箱即用的效果。你可以花10天时间来考虑界面流程如何优化,这样对于10万用户而言,节省的时间和价值都大多了。
2. 某个消息会打断或增加用户工作步骤吗?
3. 用户在等待期间该做点什么?例如显示进度条,注意不要把进度条本身设计得过于庞大,一般建议使用“正在上传......”,“载入中.......”等等这样的纯文本就可以了。
小结
本文列举了12类加速网站的办法,在实际工作,可以视具体情况灵活运用,当你完全掌握了这些方法后,也许会触类旁通,引起你深入地追寻更深层次的加速办法。
本文内容由Google提供,黄永兵整理编译。
【编辑推荐】