为何要保持标签整洁
客户端的优化近来倍受关注,可是有些较基本的方面却被忽视。如果你仔细观察某些页面(即便是那些本来应该深度优化的页面),很容易就能在他们的标签中找到一大堆冗余的、不高效的结构。所有这些累赘给本来应该尽可能保持轻量级的页面增加了不必要的负担。
保持文档整洁的原因不一定是为了更快的加载速度,更是为了让我们的建筑能有一个结实而牢固的基础,整洁的标记意味着更好的可访问性,更方便的维护,更易被搜索引擎检索,更小的体积仅仅是保持文档整洁的一个附加属性,也是我们应该这样做的另一个理由。
这篇文章里面,我们来瞧瞧html该如何优化:去掉一些不好的标记习惯、通过删除冗余的结构来减小文档体积,并应用压缩技术,我们来瞧瞧现有的压缩工具,并分析他们做得好和做得不好的地方,我们也会探讨一下在将来可以怎么做。
一团糟的写法
来看看有哪些最容易犯的错误
1.在script标签内放html注释
当今一个最严重的冗余代码是在script标签内放置HTML注释——<!-- -->
,这里不需要说得太多,一句话足够:需要通过这种方式来阻止错误的浏览器(例如‘95 Netscape 1.0)几乎灭绝了,脚本内的注释是完全不必要的累赘,应该被毫不留情地删除掉。
2.<![CDATA[ … ]>
块
另外一种常见但是不必要的阻止错误的方式是将CDATA块放到script标签内:
<script type="text/javascript">
//<![CDATA[
...
//]]>
</script>
这个神圣的目标不切实际。尽管CDATA块是一个阻止XML解析器将<
和&
当成标签的开头的一个很好的方式,但是只有在真正的XHTML文档(被当成“application/xhtml+xml” content-type来处理的文档)内才有效。目前大部分的网页仍然被当成“text/html”来处理的(因为,举个例子,IE目前仍然不支持 XHTML),所以他们都被当成HTML来解析,而不是XML。
除非你已经把文档当成"application/xhtml+xml"
来处理,否则几乎没有任何理由把CDATA块放在里面,即便你打算以后使用XHTML,也有必要先把这些不必要的东西去除,等到真正需要的时候再加进来。
当然,一个终极的解决方案是完全避免使用内联的script标签(为了利用外部script的缓存机制)。
3.类似onclick="…", onmouseover=""
这些
html事件属性有一些合理的应用场景,比如为了性能上的考虑或者为了应付古老的浏览器(尽管我还没有发现只支持html事件属性注册方式——onclick="..."
而不支持dom节点属性注册方式element.onclick = ...
的情况)。除了一些众所周知的拒绝它们的理由如行为和内容的分离或者便于重用,它还会污染html标记。把事件逻辑转移到外部的脚本里面,我们可以充分利用脚本的缓存机制。事件处理逻辑不必在每次的页面请求中都被传送到客户端。
4.onclick="javascript:…"
javascript 中一个很有趣的怪现象:javascript:
伪协议和html事件属性处理器混合成了这样一个怪胎(发生的几率高达106,000 (!)),事实上html属性上的事件处理器会整体变成一个函数体,然后这个函数变成一个事件处理程序(通常,会先把函数的作用域加强来包含元素本身和它的部分或全部的父级)。“javascript:
” 这部分变成了一个不必要的标签并且几乎没有任何作用。
5. href="javascript:void(0)"
继续javascript伪协议,有一个很著名的代码片段:href=”javascript:void(0)”,他的作用是阻止链接的默认行为。当javascript被禁用、不可用或出错的时候这个糟糕的写法使得链接完全不可访问。毫无疑问最理想的处理方法是将合适的url写入href里面,然后通过事件处理器来阻止链接的默认行为。另一种情况,如果链接是动态创建之后插入到页面中(或者最初是隐藏的,然后才通过 javascript让其显示),使用href=”#”比用javascript:更整洁,更快速。
6.style="…"
使用style属性并没有什么太严重的错误,只是如果把它里面的内容转移到一个外部的样式表里面,我们就能利用到源代码的缓存机制。这跟前面提到过的 html属性事件处理器类似。即便你只是为了给一个特定的元素设定样式并且没有打算重用它们,这些样式信息会在每次页面被请求的时候都传送一次。把样式转移到外部样式表里面能防止这个,因为外部样式表传送一次之后会缓存到客户端。
7.<script language=”Javascript” … >
也许一个最搞不懂的属性就是script标签的language属性,这个古老的属性早在1999年、10年前官方推荐HTML 4.01标准的时候就被抛弃了,除非在必须指定javascript版本(这个也不太可靠,应该尽可能避免)这种极少的情况下,否则没有任何理由继续使用这个属性。
8.<script charset=”…” … >
另一个搞不懂的属性是script的charset属性,有时候我会见到下面这种结构:
<script type="text/javascript" charset="UTF-8">
...
</script>
这里要说的是charset属性只放在“外部”script标签(有写src 属性的script标签)上才有意义。HTML 4.01 甚至说:
注意charset属性指定了src属性所指定的脚本的字符编码;它不关心script标签的内容。
测试表明浏览器都遵循了这条标准。
搜索一下这个结构,出现了2000次。当你发现像Textmate这些流行的编辑器里面都错误地使用了charset之后就一点都不觉得奇怪了。
9. 附加的优化方法
刚才我们说到了一些应该避免的不好的写法,但是还有一些,那就是删除冗余的部分。下面提到的一些优化是有些争议的,因为他们主要出于体积上的考虑。所以我把它们放在这里不是作为建议,而是作为一种选择。请各位三思而行。
1. <style media=”all” …>
HTML 4.01定义了style上的media属性,用来指定特定的媒介——屏幕、打印机、手持设备等等。media的一个可能的值是all,这个值恰好是现代浏览器的默认值,如果你发现自己在使用media="all"
,那么你可以省去它,让浏览器自动帮你设置
有趣的是,HTML 4.01说media的默认值是"screen"
。但是我测试的每一款浏览器都没有按照这条标准来执行,而把默认值设定成了"all"
。这可能就是为什么HTML5草案把默认值指定为"all"
,来和浏览器的行为达成一致。
2.<form method=”get” …>
另一个默认值——GET——form 的"method"
属性常常是被明确设定的。其实除了不够清晰,省去它没有其他坏处。注意HTML 5草案没有更改这个行为。
3. <input type=”text” …>
INPUT元素的type属性默认值为"text"
——在HTML 4.01和HTML 5草案里面都是如此。去掉这个属性对有很多文本输入框的页面来说能减少很大一部分体积。
4.<meta http-equiv=”Content-type” …>
指定页面的字符编码总是给人很大的困惑。和我们想当然的相反,META元素上指定的Content-type没有 HTTP头里面指定的“Content-type”的优先级高。当二者(HTTP头和META元素)都被设定的情况下,HTTP头优先。
如果你能控制服务器返回并且能够正确地设定HTTP头的Content-type,那么省略META标签也是可以的。保留它的唯一原因是为离线浏览该页面的时候指定编码。
5.<a id=”…” name=”…” …>
把”name”属性和”id”属性一起使用的主要原因是为了兼容古老的浏览器(如Netscape 4)。他们不能链接到用“id”指定的锚点,所以必须设置”name”,如果你的页面有有元素既设置了”id”又设置了”name”,并且不在意这些古老的浏览器,那么你可以摆脱这个古老的写法了。
注意一些副作用。如果你在脚本中通过name来查询元素(document.getElementsByName
,document.evaluate
,document.querySelectorAll
等等)把name替换成id可能会把事情搞砸。还要记住document.anchors
只返回带有name属性的元素。
6.<!doctype html>
一年多以前,Dustin Diaz建议使用HTML 5文档类型,作为减少页面体积的一个途径。这不是一个重要的优化方式,但是如果你不在乎是否通过检验并且需要使页面尽可能小巧,使用<!doctype html>
是一个可行的备选方案。测试表明这个奇特的文档声明可以触发大量的浏览器的标准模式。
激进的优化方法
如果你渴望更多的,这里还有一些比较极端的方法。有一些(如略去选填的标签)已经出现过一段时间了,还有些之前没出现过。尽管这些做法看起来有些唐突,但是它们都可以通过检验,如果你的页面是HTML的而不是XHTML的。但是你的页面是HTML的,不是吗?;)
- 去掉HTML注释
- 去掉/减少空格
- 省去选填的结束标签(
<p>
→<p>foo
) - 如果允许,去掉属性值的引号 (
<p class="foo">
→<p class=foo>
) - 删除布尔类型的属性的选填值(
<option selected="selected">
→<option selected>
) - 转移内联样式、内联脚本、和html事件属性 (如果不能删掉它们的话)
- 转移class和id(需要同步修改脚本和样式)
- 把URL的协议名称去掉 (
http://example.com
→//example.com
)
但我们可以压缩
当页面压缩之后这些优化还有意义吗?难道gzip不是已经把这些冗余的标记都搞定了吗?毕竟,我们说的是一个文本格式的东西。
用处还是有的!
首先,必须清楚不是每个人都能gzip,这是很悲哀的事情,但好的一面是在这种情况下HTML 优化的意义就更大了。
其次,即便页面压缩了,我们还可以节省5-10kb(平均每个页面)。在比较大的页面中还可以节省得更多。尽管看上去也没有太多,但实际上每一个字节都很重要。
作为一个压缩大型页面的例子,我优化了一下非官方的ECMA-262, 第三版规范的HTML版本,这个页面最初为750KB(gzip之后为131KB),优化之后为606KB(gzip之后115KB)。也就是说gzip之后节省了16KB,仅仅是去掉空格、注释、属性值的引号、选填标签。你可以看看这个压缩后的版本和以前的版本看起来是一模一样的。
最后,像去除空格和注释这样的优化能使得文档树更轻量,潜在地增强了页面的渲染效率。
物极必反
像其他一些优化一样,我们很容易矫枉过正。HTML Compact 就是HTML压缩工具里面做得太过的一个例子,这个优秀的windows程序用了一种很“独特”的方式来压缩HTML——用javascript把内容写入到页面中。
把这个完美的页面:
<html>
<head>
<title></title>
</head>
<body>
<div>
<ul>
<li>foo</li><li>bar</li><li>baz</li>
<!-- few more dozens of list elements ... -->
</ul>
</div>
</body>
</html>
变成这副模样:
<!--hcpage status="compressed"-->
<html>
<head>
<SCRIPT LANGUAGE="JavaScript" SRC="hc_decoder.js"></SCRIPT>
<title></title>
</head>
<BODY>
<NOSCRIPT>To display this page you need a browser with JavaScript support.</NOSCRIPT>
<SCRIPT LANGUAGE="JavaScript">
<!--
hc_d0("Mv#d|\x3C:,&c@w4YFAtD1 [... and so on, another couple hundreds of characters ...]");
//-->
</SCRIPT>
</BODY>
</html>
不用说,像这样的“优化”永远不应该在网络上出现,除非你的目的是降低页面对用户和搜索引擎的可访问性。并且里面的NOSCRIPT标签也让人很不爽,这种东西对某些能阻止javascript的防火墙来说也是不起作用的。馊主意,坏做法!
上面是一个很好的反例,但是还有一些也是应该注意的:
反面教材
1. 去除文档声明
HTML Compresor里面有一个选项——默认是开启的——去除文档声明。我实在想不出这么做会有什么的好处,相反,没有文档类型会激活怪异模式,使页面的布局和行为都出现很严重的问题。文档类型要么放着不动,要么被替换为一个更简短的——HTML5声明。
2.把STRONG用B替代,EM用I替代
还是那个HTML压缩工具里面有一个有害的选项是把元素用比他们更短的“替代品”来替换。问题是B不是STRONG的替代品。I也不是EM的替代品。 STRONG和EM是有语义的——表示强调,而B和I只是去改变字体样式的;他们能改变文字的展现,但是没有任何语义。
虽然浏览器把他们显示成一样的,但是屏幕阅读器和搜索引擎知道他们的区别。
3.去除title,alt属性和label元素。
一条比较重要的原则是如果优化会损失可访问性就不要优化。你可能很想删掉IMG上的“alt”属性,或者链接上的“title”属性,但是节省少量的字节相对于损失的可访问性来说的确是得不偿失。