挣脱浏览器的束缚(4) - 王道!动态添加script元素
2007-01-25 01:19 Jeffrey Zhao 阅读(8726) 评论(57) 编辑 收藏 举报我们已经知道,脚本文件的并行下载能够提高页面的加载速度。但是目前还有一个急需解决的问题,那就是对于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的重要性可以说被提高到了一个前所未有的高度。
那么我们现在就用语言来简单描述一下应该如何实现这样的效果:
-
首先,在页面上用传统方式(最好使用document.write)加载所需要的基础脚本以及所有的HTML,这时候所有的模块处于Loading状态。
-
在window.onload事件被触发后,动态加载每个模块所需的脚本。我们只需要在IE浏览器中响应script元素的onload事件或者在其它浏览器中响应script元素的onreadystatechange事件,就可以捕捉脚本文件的加载情况。
-
在上述事件的handler中,如果script元素的readyState为"complete"或"loaded"(script元素的readyState使用字符串表示),那么判断某个模块需要的脚本有没有加载完毕,如果完毕了,则显示那个模块的具体内容。
大体方式就是这样,逻辑非常简单,不过在编码上可能就会遇到一些问题。不过对于使用ASP.NET AJAX的开发人员可能就略有福气些了,因为ASP.NET AJAX内置就有动态添加脚本元素的机制,已经实现了很好的跨浏览器特性。它们能够稍稍便于我们的开发,有机会我将详细的介绍它们,并且和大家一起来设计和实现一个好用的脚本库。
我对于加载脚本文件的优化心得就只有这些了,不过我们还可以在其他方面进行优化。例如,AJAX应用里最常见的XMLHttpRequest对象,我们也可以有技巧地使用它。不过这些内容,就要等下次再和大家分享了。:)