High Performance Web Sites Rule 14

本书的规则一样适用于 Web 2.0 的页面。本章介绍怎样将前述的规则应用到 Ajax 当中去。

首先,除了 Rule 3 外,注意给每条 AJAX 请求应用以下几条规则:

接下来单独讨论怎么应用 Rule 3。虽然 AJAX 使加载完毕之前的友好响应的 UI 成为可能,但并不代表使用了 AJAX 就一定更好。每发出一条 AJAX 请求直到完成都需要一定的等待时间。

以雅虎邮箱为例:所有的联系人列表都是预先加载好的,邮箱中前三封来信也会预先下载好。如果这时点击了第四封邮件,才会发出即时的请求。

再以 Google Spreadsheets 为例,用户每访问或创建、修改一份文档都会产生同样多的 AJAX 请求,即便之前访问过它,且从未修改。

书中建议:类似的用户私有的数据也应进行缓存,用时效很长的 Expire Header,并通过 SSL 连接保护隐私。为了保证用户取缓存时,取到的东西正确,被缓存的 URL 应该包含类似:/ar?id=[snip…]&srow=0&erow=100&t=1177458941 的 Query String,即包含用户 ID、文档(或邮件)的唯一 ID、版本号等。

image

High Performance Web Sites Rule 13

Entity Tags

Entity Tags (ETags) 是 HTTP/1.1 的新特性。在 Conditional GET Requests 中起作用,是一个标识文件特定版本的标记。是除了最后修改时间以外,判断组件是否变化的另一种方式。

 

image
图1 Rule 3 提到的 If-Modified-Since 请求判断文件是否已修改

 

image
image
图2 如果第一次请求时服务器返回了 ETags 那么每次的 Conditional GET Request 浏览器都会多一个 If-None_Match 请求头判断 ETags 是否改变

 

Etags 的问题

如果网站使用多个服务器,Apache 和 IIS 的默认配置都会导致不同服务器的同样文件有不同的 ETags,从而大大增加了不必要的流量。
此外,若 ETags 的优先级高于 Expire Header (本章没说清啥情况下 ETags 的优先级高于 Expire Header),则尽管遵循 Rule 3 为基本不改动的组件设了长 Expire Header ,访问者每次刷新还是会重新下载一次那个组件。很浪费有木有?

解决

要就充分利用 ETags 带来的灵活性,比如根据不同的浏览器给出不同的 ETags (例如谷歌就给中文和英文网站的资源不同的 Etags,可以想象到带来的维护上的方便性);
要么删掉 ETags 。

image

《High Performance Web Sites》之 Rule 12 读书笔记

本章建议 Remove Duplicate Scripts,去掉重复的脚本。重复的脚本从两个方面降低网站的访问速度:
1、不必要的 HTTP 请求;
2、无用的 JavaScript 运行时间。
其中,第一条当浏览器是 Internet Explorer 且该脚本没有设缓存时会发生。看过 Rule 3 的童鞋们都会用缓存,所以此条略去 —— 除非用户点了“刷新”按钮(如 图1)。

image 图1  加载完含设置了缓存的重复脚本的网页后点“刷新”的加载结果 – FireFox 7.0.1

而第二条无视重复的脚本是否有缓存,都会在 FireFox 和 Internet Explorer 中发生。

此外,重复脚本比一般人想象的要多,甚至可能以不同域名的形式存在(如 图2)。书中统计了国外流量最高的几个网站,全都有重复的脚本存在(本书出版于 2007 年 9 月)。
image
图2 重复的脚本

最后,书中给出一种 PHP 管理 JavaScript 脚本加载的示例代码,建议每次插入 JavaScript 脚本都调用相应的函数,判断是否存在、版本是否一致后再插入,作为解决方案。

不过,在大团队中,插入 JavaScript 这件事情发生的环境本身就是复杂多样的,要做到完全都调用后台函数来插入 JavaScript 调用, 还是比较困难的吧!

image

《High Performance Web Sites》之 Rule 11 读书笔记

Avoid Redirects 的意思是避免跳转,跳转使得网页变慢。原因如 图1 ,所有下载都被阻塞了,比 Chapter 5Chapter 6 所提到的放在页尾的 CSS 样式表放在页首的外链 JavaScript 文件造成的影响还大。

image
图1  所有下载都被阻塞直到跳转完成

更好的替代方案:
1、目录 URL 不加 / 的话会自动 301 跳转到加 / 的地址,例如 http://astrology.yahoo.com/astrology 自动跳转到 http://astrology.yahoo.com/astrology/  ,
解决方案:加上 / (紧跟域名后面的 / 缺少不会引起跳转);
2、当重写后端导致接口替换时,用以下三种方案替代跳转:
(1) Alias、 mod_rewrite 和 DirectorySlash;
(2) 如果新老后端在同一服务器上,直接用后端代码链接起来;
(3) 如果只是更改域名,设置 CNAME 使得老后端地址自动指向新后端;
3、如果是统计网页站内进入来源,可以用 Referer Log 实现;
4、如果是统计站外链接的点击信息,用 “XMLHttp-Request Beacon”会稍微快些;
如果是链接是打开新窗口的,可以直接用 beacon 图像协议:

<a href=http://en.wikipedia.org/wiki/Performance
onclick="resultBeacon(this); return false;">Performance - Wikipedia</a>
<script type="text/javascript">
var beacon;
function resultBeacon(anchor) {
beacon = new Image( );
beacon.onload = gotoUrl;
beacon.onerror = gotoUrl;
beacon.src = "http://rds.yahoo.com/?url=" + escape(anchor.href);
}
function gotoUrl( ) {
document.location = beacon.src;
}
</script>

以上这段代码并不比跳转快,但对于打开新窗口的链接,却不会阻塞页面加载;

5、如果是用短域名跳转到长域名地址,使得网址更好记的情况,阅第二条;

总之,避免跳转。

image

 

后记

XMLHttp-Request Beacon 详见 http://stevesouders.com/hpws/xhr-beacon.phphttp://stevesouders.com/hpws/redir-beacon.php

关于 301 302 等跳转以及 mod_rewrite 等的进一步信息,请自行阅读《High Performance Web Sites》或者自己 google 😀

《High Performance Web Sites》之 Rule 9 读书笔记

本章的主题是 Reduce DNS Lookups ,即减少 DNS 解析。

DNS 解析平均要花TTL 一半的时间,而 DNS 缓存生效的时间依各个浏览器的不同而不同,并且被许多复杂的因素影响着。

本章也就是 Rule 9 和 Rule 6 所提倡的将 js、图片 等组件放到多个域名下,以提高并行下载数的做法相冲突。因为域名越多,DNS 解析所需的时间就越多。大多数网站都不是像谷歌首页一样只有两个组件,所以推荐使用 2 – 4 个域名就可以了。

最后一个影响因素是 Keep-alive。Chapter B 中提到使用 Keep-alive 能很好的改善 TCP/IP 的响应时间。在此,它还减少了 DNS 解析的次数,尤其是对于 FireFox 用户而言。

总之,将组件分别放在 2 – 4 个域名上,并确保服务器支持 Keep-alive 吧!

image

《High Performance Web Sites》之 Rule 10 读书笔记

本章的主题是 Minify JavaScript (压缩 JavaScript)。本章认为,不管是单独的 js 文件,还是直接嵌入到 HTML 之间的代码,都应该被 minified。

这里的压缩指的不是 Gzip 压缩,而是从代码本身的字符数下手,进一步减小其被 Gzip 压缩后的尺寸。

Minified JavaScript 被移除的有注释、多余的空白和回车;obfuscation 是另一种选择,压缩率更高,但有可能会造成 bug。

例如,最初的代码:

 

YAHOO.util.CustomEvent = function(type, oScope, silent, signature) {
this.type = type;
this.scope = oScope || window;
this.silent = silent;
this.signature = signature || YAHOO.util.CustomEvent.LIST;
this.subscribers = [];
if (!this.silent) {
}
var onsubscribeType = "_YUICEOnSubscribe";
if (type !== onsubscribeType) {
this.subscribeEvent =
new YAHOO.util.CustomEvent(onsubscribeType, this, true);
}
};

 

Minify 以后:

 

YAHOO.util.CustomEvent=function(type,oScope,silent,signature){this.type=type;this.
scope=oScope||window;this.silent=silent;this.signature=signature||YAHOO.util.
CustomEvent.LIST;this.subscribers=[];if(!this.silent){}
var onsubscribeType="_YUICEOnSubscribe";if(type!==onsubscribeType){this.subscribeEv
ent=new YAHOO.util.CustomEvent(onsubscribeType,this,true);}};

 

如果选择 obfuscation :

YAHOO.util.CustomEvent=function(_1,_2,_3,_4){
this.type=_1;
this.scope=_2||window;
this.silent=_3;
this.signature=_4||YAHOO.util.CustomEvent.LIST;
this.subscribers=[];
if(!this.silent){
}
var _5="_YUICEOnSubscribe";
if(_1!==_5){
this.subscribeEvent=new YAHOO.util.CustomEvent(_5,this,true);
}
};
 
本书统计,对于大部分 JavaScript , obfuscation 的压缩比只会高出几个百分点。而且这个差距在使用了 Gzip 压缩后会变得几乎没有,甚至为负。

相比于单纯的 Gzip 压缩(见 rule 4, 能减少约 70% 的大小), minify 压缩对 Gzip 压缩后的增益是比较微不足道的,这就是为什么 Gzip 压缩是 rule 4 而 minify 压缩是 rule 10。然而,随着 js 尺寸的增大,minify 能帮助减小的尺寸就越多。

最后,本章补充了 minify CSS。书中认为,CSS 文件往往没有那么多的注释和空白。真正的压缩还是要借助于去掉无用的 class 等,而这个所需的工具尚需研究(其实现在已经有 CSS 的管理工具可以做到)。因此书中的结论是,CSS 中用 “#606”代替 “#660066”,用 “0” 代替 “0px”等。

《High Performance Web Sites》之 Rule 8 读书笔记

终于开始在北京一间小屋里平静过自己的小日子(2011-07-16 星期六),想把火车上看过的书的笔记补一下,免得有一种残缺感。 Rule 8 说的是外链 JavaScript 和 CSS , 而不是直接嵌入。

其实外链和嵌入是各有优点的:前者虽然增加了 HTTP 请求,却增大了有 cache 和 expire header 时的加载效率(配合缓存),外链的 js 的代码重用性也更好;后者只适合基本没什么重用的 JS 和 CSS。

所以本章认为,外链在大部分情况下好过内嵌。然而,也有一个折衷的办法,叫做:首页内嵌、预先加载。也就是在首页嵌入 JavaScript 和 CSS 代码,并在加载结束后,预先为其它页面下载好它们会包含的 JavaScript 和 CSS 文件。

 

《High Performance Web Sites》之 Rule 6 读书笔记

本章主题:Put Scripts at the Bottom(将脚本放到末尾)。和《High Performance JavaScript》第一章的重点内容相同,并被引用。

其中心思想在于,浏览器为了按正确顺序下载和执行脚本,遇到 script 代码块的时候,浏览器会屏蔽下载活动,等待该脚本下载执行完毕,如下两张图。

image

image

继续用图说话,如果将 script 引用和代码块都放在页面尾,就变成:

image

显然,脚本在其它所有组件都显示出来了以后才被下载和执行,相信和 Rule 5: 将 CSS 文件放到页首 一样,访客会感觉快很多。

引申问题:为什么图中并行下载的 component (图片也算 component)只有两个? 此问题可以回到 Rule 1 来解答。

HTTP/1.1 规定,每个域名下最多同时下载两个 component 。大部分网站都是 HTTP/1.1 的。这时如果将 component 分布在多个不同的域名下,可以在增加服务器负担的同时提升 HTTP 并行数。经过 Yahoo! 的研究,域名数为 2 时效果最佳。

另外,访客可以修改浏览器在每个域名下最多同时下载的 component 个数,来达到加速的目的。

《High Performance Web Sites》之 Rule 7 读书笔记

本人从此章第一次知道世上有种叫做 CSS expression 的东西,示例代码如下。

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

本章的主题就是“Avoid CSS Expressions”(避免使用 CSS expression )。CSS expression 是 IE only,很适合用来做跨浏览器兼容,比如 IE 不支持的 min-width 属性:

P {
  width: expression( document.body.clientWidth<600 ? "600px" : "auto" );
  min-width: 600px;
}

那么 CSS expression 坏在哪里呢?在于它的计算次数过多(包括鼠标移动、滚动条位置改变、放大缩小窗口等待),并且可能锁死浏览器。本书有一个计算次数和锁死的官方示例:http://stevesouders.com/hpws/expression-counter.php

如何避免?事实上,本书认为每个需要用到 CSS expression 的地方都会有替代方法,本书给出了两种:

1、只计算一次的 expression (官方演示地址:http://stevesouders.com/hpws/onetime-expressions.php

<style>
P {
    background-color: expression( altBgcolor(this) );
}
</style>
<script type="text/javascript">
function altBgcolor(elem) {
    elem.style.backgroundColor = (new Date()).getHours( )%2 ? "#F08A00" : "#B8D4FF";
}
</script>

2、Event Handler (官方示例: http://stevesouders.com/hpws/event-handler.php

function setMinWidth( ) {
    setCntr( );
    var aElements = document.getElementsByTagName("p");
    for ( var i = 0; i < aElements.length; i++ ) {
        aElements[i].runtimeStyle.width = ( document.body.clientWidth<600 ?
"600px" : "auto" );
     }
}
if ( -1 != navigator.userAgent.indexOf("MSIE") ) {
    window.onresize = setMinWidth;
}
 
以上代码使得每次窗口大小改变时,段落的宽度会自动调整。但是由于在第一次加载的时候不会初始化,需要和上一种方法配合使用。

 

《High Performance Web Sites》之 Rule 5 读书笔记

这章说的是将 CSS Sheet 以 <link> 标签的形式放到 <head> 和 </head> 之间。

提速指数: 略慢于将 CSS Sheet 放在末尾或者以 @import 的形式。

实际好处:在 IE 中,避免网页在加载过程中一直显示空白直到所有组件下载完毕,使访客感官上觉得更快。

起效时机:打开新窗口时、刷新页面时、浏览器加载首页时。

为什么 IE 会选择在另两种情况下让网页先白屏呢?原因是样式表会改变 components 的显示方式。当 CSS 文件全部下载完毕以后,浏览器要按其规定对网页上 components 进行形式重排。在此之前,如果贸然显示已下载的内容,访客就会目睹这个格式重排的过程。

IE 选择了不让访客看到此过程,Firefox 则恰恰相反。但无论访客能不能看到,这个问题都是前端工程师应该去避免的。