代码改变世界

挣脱浏览器的束缚(4) - 王道!动态添加script元素

2007-01-25 01:19 Jeffrey Zhao 阅读(...) 评论(...) 编辑 收藏

我们已经知道,脚本文件的并行下载能够提高页面的加载速度。但是目前还有一个急需解决的问题,那就是对于FireFox浏览器的优化。在我们之前使用的优化方法,无论是简单实用的document.write还是食之无味的defer属性,FireFox浏览器都对此置若罔闻。不过FireFox也不是绝对地“冥顽不灵”,开发人员还是有方法对它进行优化的。

这个方法就是动态添加script元素。

动态添加script元素

不知道“动态添加script元素”这个说法是否正确,我在这里的意思是使用JavaScript编程,向<head />里添加script元素。下面的代码动态添加了5个script元素:

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server" id="head">
    <title>Untitled Page</title>
    <script type="text/javascript" language="javascript">
        for (var i = 0; i < 5; i ++)
        {
            var script = document.createElement("script");
            script.type = "text/javascript";
            script.src = "Script.ashx?a=" + i;
            document.getElementById('head').appendChild(script);
        }
    </script>
    
</head>
<body>
    ...
</body>
</html>

 

请注意,由于在JavaScript代码执行时页面还没有加载完毕,因此还不能使用document.getElementsByTagName方法来获得head元素,我们只能为head元素添加一个id,并使用document.getElementById方法来获得它。打开这张页面,就会发现,无论是IE(图9)还是FireFox(图10)的元素加载都会发现优化的效果:


图9:IE中动态加载script元素效果


图10:FireFox中动态加载script元素效果

我们姑且不关心为什么FireFox中每个脚本文件会使用2.5秒进行加载,但是并行加载的效果切切实实的出现了!加上多域名,效果更明显。

细心的朋友不知道回想起什么了吗?没错,当年在ASP.NET AJAX某个版本中(我记得是Beta 1,有些模糊了)加载自定义脚本时使用了Sys.Application.queueScriptReference方法,它能够让脚本文件并行加载。但是由于接下来会谈到的几个问题,最终还是选择了传统的加载方式。不过ASP.NET AJAX还是细心地考虑到脚本加载的影响,ScriptManager和ScriptReference已经提供了LoadScriptsBeforeUI属性,我们现在就能够控制script元素是出现在UI之前还是之后了,我们可以影响性能但是无需“急用”的脚本放在所有UI的最后进行加载,以降低它对于性能的影响(这个是在刚发布的ASP.NET AJAX正式版中新增的功能,我在阅读代码时无意发现)。

再说句题外话,虽然这个脚本加载方法已经被取消了,但是功能依旧存在,因为UpdatePanel在Partial Rendering之后只能选择动态加载脚本文件。我们也能够自己使用这样的加载方式,然而这就超出了今天这篇文章讨论的范围。不过既然ASP.NET AJAX正式版已经发布了,我也能够放心的继续《深入Atlas系列》了。:)

 

动态添加script元素的缺陷

世界上很少有完美的事物。动态添加的script元素能够使IE和FireFox里都得到优化,它应该也会有些麻烦,否则为什么这个方法没有被推广呢?

而且事实上,动态添加script元素的做法是“优化难度”最高的方法。我现在就来一一列举这些“缺陷”:

1、无法阻碍页面加载

其实这个问题和在IE中使用defer属性遇到的问题相同。如果您需要在页面中直接使用脚本文件里定义的函数,就不能轻易使用这个做法。即使它的确能够优化页面的加载速度。

2、影响window.onload事件的触发

如果对于window.onload事件的处罚有所影响,但是这种影响能够在不同浏览器中得到统一倒也罢了,还相对容易处理一些。现在的问题就在于,在IE中,window.onload事件会在页面其它元素被加载完毕之后立即触发,而FireFox里的window.onload事件会等待动态添加的那些脚本文件也被加载完毕后才触发。虽然我们开发人员是伟大的,可是要兼容这两种情况依旧不是一件易如反掌的事情。

3、动态加载脚本的执行顺序

这一点才是最致命的。

虽然我们动态加载的script元素是有严格顺序的,但是浏览器可不一定这样认为。在FireFox中,脚本文件会按照它动态加载的script元素的顺序执行,而IE会根据脚本文件下载完毕的顺序执行。

那还得了?

 

那么为何称之为王道?

既然麻烦这么多,为什么还称之为“王道”?其实我们只要合理的使用这个方法,就能够大大提高页面的Perceived Performance。

可能在这里我需要重新定义一下“Perceived Performance”的概念。它的意思是“用户感受到的性能”。我们打开一个页面,例如Windows Live个人主页,会发现页面的框架都被加载了,但是每一个框架都是Loading状态。然后每一个模块陆陆续续地加载成功。

我们来想象一下这个场景。一个页面的所有内容(包括模块),需要20秒钟才能加载完毕。但是它用了10秒钟就显示出了模块的框架,在接下来10秒钟内每个模块慢慢的出现。还有一种情况,就是等待整整20秒才能看到页面。从用户角度来看,哪个性能比较高呢?

这个就是Perceived Performance的经典应用。从所谓的Web 2.0开始,Perceived Peformance的重要性可以说被提高到了一个前所未有的高度。

那么我们现在就用语言来简单描述一下应该如何实现这样的效果:

  1. 首先,在页面上用传统方式(最好使用document.write)加载所需要的基础脚本以及所有的HTML,这时候所有的模块处于Loading状态。
  2. 在window.onload事件被触发后,动态加载每个模块所需的脚本。我们只需要在IE浏览器中响应script元素的onload事件或者在其它浏览器中响应script元素的onreadystatechange事件,就可以捕捉脚本文件的加载情况。
  3. 在上述事件的handler中,如果script元素的readyState为"complete"或"loaded"(script元素的readyState使用字符串表示),那么判断某个模块需要的脚本有没有加载完毕,如果完毕了,则显示那个模块的具体内容。

大体方式就是这样,逻辑非常简单,不过在编码上可能就会遇到一些问题。不过对于使用ASP.NET AJAX的开发人员可能就略有福气些了,因为ASP.NET AJAX内置就有动态添加脚本元素的机制,已经实现了很好的跨浏览器特性。它们能够稍稍便于我们的开发,有机会我将详细的介绍它们,并且和大家一起来设计和实现一个好用的脚本库。

 

我对于加载脚本文件的优化心得就只有这些了,不过我们还可以在其他方面进行优化。例如,AJAX应用里最常见的XMLHttpRequest对象,我们也可以有技巧地使用它。不过这些内容,就要等下次再和大家分享了。:)