Hello World
Spiga

挣脱浏览器的束缚(2) - 别让脚本引入坏了事

2007-01-20 01:25 by 老赵, 7234 visits

现在哪里还找得到不引入JavaScript脚本文件的Web应用?使用脚本文件的好处多多,其中最重要的可能就是提供缓存能力了。使用脚本文件之后再加上缓存,可以大大降低数据传输量,提高页面打开的速度。不过脚本文件的引入也不是简单得不值一提,我们完全有能力来优化它。

 

小心传统的脚本引入方式带来的性能问题

现在的Web应用所需的脚本越来越多,一张页面下载几百K的脚本也不再是难以想象的事情了,这就直接导致页面需要更长的时间来加载脚本。不过传统的脚本引入方式(使用<script />)会造成什么问题?再查看这点之前,我们先写一个HttpHandler来模拟一个需要较长时间才能加载的脚本。这很简单,我们只要创建一个Http Handler来做到这一点,如下:

public class Scripts : IHttpHandler {

    public void ProcessRequest (HttpContext context) {
        context.Response.ContentType = "application/x-javascript";

        System.Threading.Thread.Sleep(1500);

        context.Response.Write("//");
    }

    public bool IsReusable {
        get {
            return false;
        }
    }
}

 

我使用Thread.Sleep函数使线程休眠1.5秒,然后输出一个注释符。这样就保证了页面加载该文件需要比较长的时间,也可以将脚本的执行时间降到最低。

然后我们就写个最简单的页面,来测试一下加载这些文件的结果:

<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server" id="aaa">
    <title>Untitled Page</title>
    <script type="text/javascript" language="javascript" src="Scripts.ashx?a"></script>
    <script type="text/javascript" language="javascript" src="Scripts.ashx?b"></script>
    <script type="text/javascript" language="javascript" src="Scripts.ashx?c"></script>
    <script type="text/javascript" language="javascript" src="Scripts.ashx?d"></script>
    <script type="text/javascript" language="javascript" src="Scripts.ashx?e"></script>
</head>
<body>
    ...
</body>
</html>

 

在IE里打开页面,看看这些脚本加载的情况。请注意,您可以使用IE Dev Toolbar来禁用Cache(图5)。


图5:IE中传统方式加载脚本的情况

真可谓是相当的整齐。不过整齐的背后是较低的性能:脚本文件一个一个被加载,所有脚本文件被加载完需要用8秒多时间。

那么FireFox的表现又如何?我们使用同样的页面来测试一下(图6)。


图6:FireFox中传统方式加载脚本的情况

嘿,情况差不多。

其实出现这个状况是By Design的。从上面这个简单的例子里可能还无法看出,事实上,当浏览器遇到<script />标签时,它会开始加载脚本文件,而此时页面的其它加载行为则会全部停止,包括HTML的呈现,页面或图片的下载等等。这是因为浏览器“怀疑”这些脚本文件中的一些行为可能会再页面中输出HTML。自然,我们可以使用document.write方法这么做。而很多可以放在网站中的第三方小部件,都是靠脚本文件里的document.write方法来生成HTML的。

这就让用户不太好受了。为什么我的浏览器只能建立一个连接?为什么不能一起下载?我们的带宽不是浪费了很多吗?这些都没错。还记得前一段时间台湾地震使一些Blog无法打开或者打开很慢吗?这很可能就是在页面中使用<script />引入脚本文件时造成的问题:文件下载特别慢,甚至会超时。而且当时我的blog也遇到这个问题。解决方案很简单,把<script />去掉便是。或者,您可以将<script />元素放置在“页尾”代码中,这样,页面就会打开地比较快了——不过当然,那个文件很可能还在继续加载脚本中。

这就是提高了所谓的“感知性能(Perceived Performance)”,简单的说,就是用户“感受”到的性能。用户会发现页面已经打开了,虽然还没有完全加载完,例如Snap Preview还无法工作。

 

尝试打破传统脚本引入的瓶颈

现在的脚本越做越大了,一个200K的文件,如果以20K每秒的速度下载也要10秒。如果这十秒结束之后又来个十秒……这样的网页加载速度太可怕了。我们必须尝试着打破这个瓶颈。

很有趣的是,如果您在页面中使用document.write来写一个<script />元素的话,这些脚本就可以并行下载了。我们就用下面的代码进行尝试吧:

<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server" id="aaa">
    <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>');
        document.write(
            '<script type="text/javascript" language="javascript"' + 
            ' src="Scripts.ashx?d"><' + '/script>');
        document.write(
            '<script type="text/javascript" language="javascript"' + 
            ' src="Scripts.ashx?e"><' + '/script>');
    </script>
</head>
<body>
    ...
</body>
</html>

 

这样的做法似乎有些复杂,不过应该还算直观。上面代码的目的就是在页面中“写入”<script />元素,以达到引入脚本文件的目的。还是用事实说话,先来看一下IE中打开页面的效果吧(图6):


图7:IE中使用document.write加载脚本的情况

状况好多了。可以看出总是有两个脚本文件在同时下载,虽然还是受制于浏览器对于每个Domain只有2个连接的限制,但是页面加载时间已经从8秒多锐减到不到5秒了。这实在是一个绝好的消息。那么再公布一个好消息,使用这种方式引入脚本文件的话,脚本文件的执行顺序与脚本文件出现的顺序相同。我们只要安排好脚本文件的顺序,这样就可以保证脚本执行的正确性了。

嘿嘿,不管怎么说这个方法还是非常容易使用的,不是吗?那么让我们欢呼雀跃吧,因为优化就是这么简单!

很可惜事情的发展并不如我们想象的那么单纯。我们还没有试过FireFox下的状况呢。看了FireFox加载页面的数据统计图,可能就会知道,我们离目标还有很大的距离——因为它的状况和图6的显示状况完全相同,document.write这种做法在FireFox里没有起到任何作用。

为什么IE的表现和FireFox的表现不同呢?可能这就要问一下浏览器的开发者了,我们现在要做的,可能只是根据结果来为我们的应用想出更好的解决方案。

路漫漫其修远兮。

Creative Commons License

本文基于署名 2.5 中国大陆许可协议发布,欢迎转载,演绎或用于商业目的,但是必须保留本文的署名赵劼(包含链接),具体操作方式可参考此处。如您有任何疑问或者授权方面的协商,请给我留言

Add your comment

43 条回复

  1. Shawn[未注册用户]
    *.*.*.*
    链接

    Shawn[未注册用户] 2007-01-20 05:19:00

    老赵,好文!好文!
    现在的js脚本确实是越写也大,网页加载也是越来越慢,是需要对如何优化有些了解,十分期待你这个系列~
    有个问题好像ajax框架在IE下面的性能好像没有firefox好,用它实现对页面元素的拖拽,如果页面上拖拽的元素多了在IE下发现会很卡,但是firefox下却反而很流畅,这是什么原因?

  2. 在北京的湖南人
    *.*.*.*
    链接

    在北京的湖南人 2007-01-20 08:41:00

    的确是我期待已久的文章!我们网站的项目经理中的开发规范就是这么要求的,但是我们一直不解其中原因,现在才豁然大悟!

    我刚写的一篇文章,有个网站运营方面的疑问,还想请教!
    http://www.cnblogs.com/FrameWork/archive/2007/01/20/625281.html

  3. 孤叶(学习.net框架)
    *.*.*.*
    链接

    孤叶(学习.net框架) 2007-01-20 09:07:00

    good boy
    good work

  4. 零度的火[未注册用户]
    *.*.*.*
    链接

    零度的火[未注册用户] 2007-01-20 09:48:00

    谢谢,很受用。 :)

  5. wuChang
    *.*.*.*
    链接

    wuChang 2007-01-20 10:26:00

    好办法

  6. 木野狐
    *.*.*.*
    链接

    木野狐 2007-01-20 11:02:00

    学习。平时还没注意到这个细节。

  7. 高海东
    *.*.*.*
    链接

    高海东 2007-01-20 13:29:00

    这个性能提高很好

  8. 老赵
    admin
    链接

    老赵 2007-01-20 15:01:00

    @Shawn
    这的确是浏览器执行JS效率的问题。
    您使用的是IE6吗?这个问题在IE6种会显得很严重,在FireFox和IE7就会好很多了。

  9. 老赵
    admin
    链接

    老赵 2007-01-20 15:07:00

    @在北京的湖南人
    这个问题的确比较麻烦,因为我们既不能禁止缓存,又要方便得使修改后的结果立即呈现。
    如果那个Flash的src是自动生成的话,您可以在src之后添加一个query string,比如file.swf?t=xxxx,xxxx表示您修改Flash的时间,这样是一个比较好的方式,在IE和FireFox下都能够得到缓存,并且及时更新。不过这样就会禁止Opera和Safari的缓存了。
    如果src是静态写在页面里的话,可能最合适的办法还是修改每一台机器的地址吧,我想……

  10. 老赵
    admin
    链接

    老赵 2007-01-20 15:10:00

    @木野狐
    关键在于,这个用法非常方便和直观,也不会有任何副作用(以后提到的方法往往都会有些需要当心的地方),而且立即使IE用户的感受得到了提高。:)

  11. 老赵
    admin
    链接

    老赵 2007-01-20 15:11:00

    @高海东
    这是个最简单的方法,不过对于FireFox无效。还有别的方法可以同时优化IE和FireFox。:)

  12. Shawn[未注册用户]
    *.*.*.*
    链接

    Shawn[未注册用户] 2007-01-20 17:07:00

    的确是IE6,怎么会这样?

    还有貌似<script defer>用defer可以告诉浏览器脚本块中不会有文档输出,让浏览器继续解析HTML

  13. 老赵
    admin
    链接

    老赵 2007-01-20 18:58:00

    @Shawn
    IE6做的不好,呵呵。
    还有您说的没错,defer的确可以让IE得到并行下载的效果,并且能够继续解析HTML。不过在FireFox还是没有效果,而且还有一些别的需要注意的问题。
    接下来的文章里我会再具体谈一下。:)

  14. Cat Chen
    *.*.*.*
    链接

    Cat Chen 2007-01-21 01:24:00

    其实script的非并行下载确实是一个严重问题,例如个人blog引用Analytics、AdSense、Snap Anywhere等的一些服务script吧,如果它不下载完,你的网页就不算加载完,这就导致了很多其它问题。然而网络服务不可能任何时候都那么好的,你的blog能访问的时候,并不意味着引用的服务script就一定能访问。

  15. 老赵
    admin
    链接

    老赵 2007-01-21 02:17:00

    @Cat Chen
    没错,必须避免或尽可能减少这些服务的问题对站点本身的影响。

  16. 臭石头
    *.*.*.*
    链接

    臭石头 2007-01-21 11:18:00

    真是一篇好文章

  17. 老赵
    admin
    链接

    老赵 2007-01-21 14:57:00

    @臭石头
    谢谢。:)

  18. 有点麻烦[未注册用户]
    *.*.*.*
    链接

    有点麻烦[未注册用户] 2007-01-21 15:49:00

    老赵能说说在个性化主页中是如何保存用户信息吗?(为了让每个用户登陆后看到的东西都不一样...如google.live个性化主页)

  19. 老赵
    admin
    链接

    老赵 2007-01-21 16:30:00

    @有点麻烦
    您使用任意的方法提取,再用任意方式保存就可以了,其实完全随意……似乎是依据废话。
    例如您可以记录有多少列,每一列有哪些模块,然后保存在数据库中。

  20. 有点麻烦[未注册用户]
    *.*.*.*
    链接

    有点麻烦[未注册用户] 2007-01-21 17:11:00

    @Jeffrey Zhao
    谢谢,我得试试

  21. 老赵
    admin
    链接

    老赵 2007-01-21 19:12:00

    @有点麻烦
    :)

  22. 小生
    *.*.*.*
    链接

    小生 2007-01-22 08:00:00

    博主的時間圖是怎么畫的呀?

  23. 老赵
    admin
    链接

    老赵 2007-01-22 09:15:00

    @小生
    在Excel里画条形图。:)

  24. charleschen
    *.*.*.*
    链接

    charleschen 2007-01-22 10:41:00

    Good!受教了!

  25. Go_Rush
    *.*.*.*
    链接

    Go_Rush 2007-01-22 11:16:00

    博主的方法不错, document.write

    但是所谓的并行下载,难道是8个js同时下载吗。
    应该是两个js 并行下载吧。

    因为ie默认只能对同域的两个资源进行同时请求的哦。
    难道 document.write 能突破这个限制?

    http://community.csdn.net/Expert/topic/5215/5215633.xml?temp=.1010095

  26. 老赵
    admin
    链接

    老赵 2007-01-22 11:24:00

    @charleschen
    谢谢。:)

  27. 老赵
    admin
    链接

    老赵 2007-01-22 11:25:00

    @Go_Rush
    没错,每个域名2个连接是浏览器的限制。我们可以通过增加域名的方式来缓解这个问题。:)

  28. Go_Rush
    *.*.*.*
    链接

    Go_Rush 2007-01-22 11:50:00

    不好意思,我看文章挺粗心的,看了你的回复,又看了一下你的文章。
    才发现原来你在文中已经提到过2连接限制的问题了。

    对于大型网站,增加域名是很普遍的做法。
    很多商务网站,图片都是用
    < img src="http://img.domain.com/somepath/pic.jpg">的方式来加载的。
    他们不会用 www.domain.com 更不会简单的用相对路径。
    就是为了加载页面的时候快那么一点点

  29. 老赵
    admin
    链接

    老赵 2007-01-22 12:00:00

    @Go_Rush
    下一片文章里我就会提到这一点,优化总是要慢慢来的,方式有很多种。增加Domain的确是最常用的方式之一。:)

  30. Lucky[未注册用户]
    *.*.*.*
    链接

    Lucky[未注册用户] 2007-01-25 17:48:00

    很實用

  31. 老赵
    admin
    链接

    老赵 2007-01-25 18:03:00

    @Lucky
    :)

  32. 空空鱼
    *.*.*.*
    链接

    空空鱼 2007-02-05 11:56:00

    醍醐灌顶啊

  33. 老赵
    admin
    链接

    老赵 2007-02-05 12:54:00

    @空空鱼
    :)

  34. 猪八戒[未注册用户]
    *.*.*.*
    链接

    猪八戒[未注册用户] 2007-08-06 17:31:00

    老赵你好
    我按照你上面写的例子, 把线程休眠改为5秒 ,页面添加的是13个脚本。
    结果发现它是每5个脚本并行下载的,不知道为什么呢?(Fiddler下数据如下)
    浏览器对于每个Domain不是只有2个连接的限制吗?难道限制改了吗?还有http 1.1是2个限制,http 1.0 是4个限制,我们用的都是http 1.1吗?


    00:00:00.000 0.060 585 3550 POST 200 text/html; charset=utf-8 Default2.aspx
    00:00:00.099 5.179 334 257 GET 200 application/x-javascript; charset=utf-8 Scripts.ashx?a
    00:00:00.108 5.135 334 257 GET 200 application/x-javascript; charset=utf-8 Scripts.ashx?b
    00:00:00.115 5.562 334 257 GET 200 application/x-javascript; charset=utf-8 Scripts.ashx?c
    00:00:00.124 6.054 334 257 GET 200 application/x-javascript; charset=utf-8 Scripts.ashx?d
    00:00:00.134 6.544 334 257 GET 200 application/x-javascript; charset=utf-8 Scripts.ashx?e
    00:00:00.142 10.232 334 257 GET 200 application/x-javascript; charset=utf-8 Scripts.ashx?f
    00:00:00.148 10.185 334 257 GET 200 application/x-javascript; charset=utf-8 Scripts.ashx?g
    00:00:00.152 10.560 334 257 GET 200 application/x-javascript; charset=utf-8 Scripts.ashx?h
    00:00:00.166 11.046 334 257 GET 200 application/x-javascript; charset=utf-8 Scripts.ashx?i
    00:00:00.168 11.544 334 257 GET 200 application/x-javascript; charset=utf-8 Scripts.ashx?j
    00:00:00.175 15.201 334 257 GET 200 application/x-javascript; charset=utf-8 Scripts.ashx?k
    00:00:00.181 15.211 334 257 GET 200 application/x-javascript; charset=utf-8 Scripts.ashx?l
    00:00:00.186 15.554 334 257 GET 200 application/x-javascript; charset=utf-8 Scripts.ashx?m

  35. 老赵
    admin
    链接

    老赵 2007-08-06 17:46:00

    @猪八戒
    Fiddler的数据是粗略的,假的,要么阻塞,不阻塞也只能受到2个连接的限制。

  36. 老赵
    admin
    链接

    老赵 2007-08-06 17:46:00

    @Jeffrey Zhao
    HttpWatch得到的数据是精确的。

  37. 猪八戒[未注册用户]
    *.*.*.*
    链接

    猪八戒[未注册用户] 2007-08-06 19:28:00

    @Jeffrey Zhao
    不好意思,我的一个失误,提问的时候写错了。把 HttpWatch 写成 Fiddler 工具了。

    我就是用HttpWatch Professional 4.2 版 测试的。
    结果发现它好象是 5个脚本 一起下载的,好奇怪啊。 数据下载的时间看,也好象是5个并行的。不知道为什么?

  38. 猪八戒[未注册用户]
    *.*.*.*
    链接

    猪八戒[未注册用户] 2007-08-06 19:37:00

    而且,我在服务器端设置断点,在客户端点刷新后,程序在断点停顿时;
    查看HttpWatch 工具发现,它的sent 列 数据有6列了。
    除页面行外,就是Scripts.ashx?a ,Scripts.ashx?b,Scripts.ashx?c,Scripts.ashx?d,Scripts.ashx?e那5个脚本了。
    这个情况是表明,5个脚本并行执行了吗?

    望指教:谢谢

  39. 老赵
    admin
    链接

    老赵 2007-08-06 20:47:00

    @猪八戒
    要不发个例子给我看看?

  40. thisisbody
    *.*.*.*
    链接

    thisisbody 2007-12-21 12:31:00

    恩,好文章,太有用了。

  41. George Wu[未注册用户]
    *.*.*.*
    链接

    George Wu[未注册用户] 2008-01-09 10:24:00

    弱弱地问下,<script/>引入的应该是js文件,难道.ashx文件也是?还有就是加载时候src="Scripts.ashx?a"中的a,b,c,d等又作何解?谢谢。

  42. 老赵
    admin
    链接

    老赵 2008-01-09 17:30:00

    @George Wu
    script里可以引用任何资源,只要能够获得脚本内容就可以了。

  43. Seasun海豚
    *.*.*.*
    链接

    Seasun海豚 2009-12-29 09:54:00

    Jeffrey Zhao:@George Wu
    <br>script里可以引用任何资源,只要能够获得脚本内容就可以了。


    解惑了我之前的一个疑问,我发现我学习得太零散了。

发表回复

登录 / 登录并记住我 ,登陆后便可删除或修改已发表的评论 (请注意保留评论内容)

昵称:(必填)

邮箱:(必填,仅用于Gavatar

主页:(可选)

评论内容(大于5个字符):

  1. Your Name yyyy-MM-dd HH:mm:ss

使用Live Messenger联系我