代码改变世界

挣脱浏览器的束缚(3) - 两个连接还不够“并行”

2007-01-22 14:42  Jeffrey Zhao  阅读(7795)  评论(26编辑  收藏  举报

在讨论这次的主题之前,我们现在看一下脚本优化的另一个问题,就是“优化难度”。在这里我所说的“优化难度”是指优化一张页面时的修改难度。例如在前一片文章中,使用document.write来引入脚本的话,其“优化难度”会非常的低——没有任何副作用,不用修改其它任何代码。不过它的效果似乎还不太理想,因为仅仅优化了IE下的体验,在FireFox里却没有任何作用。

很可惜,我回想了几乎所有的优化方式,再也没有找到优化难度如此低的做法了。对于其它的方式,我们都必须在页面的别处进行修改,优化效果越好,修改量越大。对于这些优化方式,我们就必须编写合适的组件,将一些逻辑封装起来。这样可以在一定程度上方便使用,降低优化难度。 

 

比较document.write与defer

那么这又何document.write或者defer有什么关系?且听我慢慢道来。

<script />的defer属性在标准里的定义是这样的:

When set, this boolean attribute provides a hint to the user agent that the script is not going to generate any document content (e.g., no "document.write" in javascript) and thus, the user agent can continue parsing and rendering.

我们当时遇到JS无法并行下载的原因就是浏览器认为在脚本中可能会输出HTML内容。defer属性的作用就是告诉浏览器,脚本里不会输出任何信息。果然,当我们在IE里使用defer属性时,脚本没有被阻塞,其效果和document.write一样。不过在FireFox里依旧不行,这样的实现实在让人费解。

都说FireFox标准,看来在细节上也不尽然。

那么为什么我们在之前使用了document.write而不是defer属性呢?两者效果相同,但是明显使用defer属性更加直观啊。

defer属性使用起来的确直观和方便。不过,效果真的相同吗?我们可以通过以下的例子试试看。

<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
    <title>Untitled Page</title>
    <script type="text/javascript" language="javascript">
        document.write(
            '<script type="text/javascript" language="javascript"' + 
            ' src="Scripts.ashx?a"><' + '/script>');
        document.write(
            '<script type="text/javascript" language="javascript"' + 
            ' src="Scripts.ashx?b"><' + '/script>');
        document.write(
            '<script type="text/javascript" language="javascript"' + 
            ' src="Scripts.ashx?c"><' + '/script>');
    </script>
</head>
<body>
    <input type="button" value="Click" />
    <script type="text/javascript" language="javascript" src="Scripts.ashx?a">
        alert('Hello World');
    </script>
</body>
</html>

 

然后再使用<script defer="defer"></script>的方式引入一下。打开两个页面进行比较就会发现,如果使用document.write的话,在脚本加载完毕之前按钮不会显示,也不会出现提示框;而如果使用defer属性的话,按钮就立即出现了,也会马上出现提示。

这可麻烦了。如果页面上的元素过早出现,用户在脚本加载完之前进行操作是否会有问题?如果页面里存在直接执行的脚本(如上例的alert调用),在脚本文件加载完之前是否能够执行?如果上面两个问题的答案有任何一个是肯定的话,那么恭喜您,使用defer属性就会造成错误了。而且这个问题的解决方案实在不太容易找到,这大大增加了“优化难度”。

而且更为关键的是,FireFox同样不支持defer属性的效果。这直接导致了defer属性全面落后于使用document.write的优化方式。既然这样,我们为什么要用它?事实上defer属性用的实在不多,这是个非常典型的的“鸡肋” 特性。

那么,哪里有使用defer属性的应用呢?我想应该是有的吧,虽然我不知道。

 

突破两个连接的限制

在上一片文章里我们可以看到,虽然document.write方法可以让脚本文件并行加载,但是它依旧受到浏览器的限制。根据HTTP协议的标准,对于同一个Domain,只能同时存在两个连接。在这点上,亲爱的浏览器们都乖乖的实现了。我们如果想要突破这种限制,就要增加域名。不过其实浏览器判断域名的方式是非常严格的,同一域名下的子域名,同一域名不同端口,都不算相同。一般来说,使用子域名来增加并行加载的连接数是比较常用的做法。

应该已经有不少朋友知道这个方法,它的应用实在太普遍了。不过请注意,请求任意资源时都会建立连接,浏览器对于某一域名的连接并不区分其作用。因此,无论下载图片,CSS文件,JavaScript文件,或者是XMLHttpRequest对象建立的AJAX连接,都属于“两个连接”之内,在优化时往往需要注意这一点。另外,一个浏览器里同时建立的连接数也不是越多越好,根据实验资料显示,浏览器可以同时建立6到7连接最为合适。因此,我们使用3到4个子域名是比较妥当的。

我们现在就来看一下使用效果。在开发时要出现这个效果,我们可以修改C:\WINDOWS\system32\drivers\etc\hosts文件来设置本地的DNS映射。如下:

127.0.0.1 www.test.com
127.0.0.1 sub0.test.com
127.0.0.1 sub1.test.com
127.0.0.1 sub2.test.com
127.0.0.1 sub3.test.com
127.0.0.1 sub4.test.com
127.0.0.1 sub5.test.com

 

我们可以多加一些子域名,方便以后使用。

接下来我们就可以在页面里从多个不同的子域名加载脚本文件,如下:

<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
    <title>Untitled Page</title>
    <script type="text/javascript" language="javascript">
        document.write('<script type="text/javascript" language="javascript"' + 
            ' src="http://sub0.test.com/Scripts.ashx?a"><' + '/script>');
        document.write('<script type="text/javascript" language="javascript"' + 
            ' src="http://sub0.test.com/Scripts.ashx?b"><' + '/script>');
        document.write('<script type="text/javascript" language="javascript"' + 
            ' src="http://sub1.test.com/Scripts.ashx?c"><' + '/script>');
        document.write('<script type="text/javascript" language="javascript"' + 
            ' src="http://sub1.test.com/Scripts.ashx?d"><' + '/script>');
        document.write('<script type="text/javascript" language="javascript"' + 
            ' src="http://sub2.test.com/Scripts.ashx?e"><' + '/script>');
    </script>
</head>
<body>
    ...
</body>
</html>

 

在浏览器打开页面试试看?还记得当初我们加载页面用了多少时间吗?8秒多!而现在已经能够在不到2秒内加载完毕了(如图2)。


图8:使用多个子域名进行并行加载

 

可惜我们还要优化FireFox浏览器里的情况,下次我们就来讨论这个问题。接下来的优化方案会有一定的难度,不过只要我们利用得当,将会大大提高Perceived Performance。