原文链接: http://www.stevesouders.com/blog/2010/12/06/evolution-of-script-loading/
Velocity China明天早晨即将拉开帷幕,我会做一个主题发言,而现在正在准备相关PPT(演示文稿)。届时我会做一些有关渐进增强和JavaScript智能加载的分享。就在复核PPT时,我惊讶地发现最近几年外部脚本(本文特指JavaScript)加载技术已发生了重大变化。现在就让我们一同来回顾过去、把握现在、畅想未来。
将脚本置于文档HEAD中
就在几年前,包括许多顶级网站在内的大部分网站都将外部脚本置于HTML文档的HEAD标签中:
<head> <script src="core.js" type="text/javascript"></script> <script src="more.js" type="text/javascript"></script> </head>
熟悉web性能最佳实践的开发者一定会对以上方式深恶痛绝。我们都知道老式浏览器(主要是IE6、IE7)是按顺序加载脚本的。浏览器先加载core.js、然后解析core.js,最后执行它。接着对more.js重复以上过程:加载、解析,然后执行。
老式浏览器除了会按顺序加载脚本外,加载过程中还会阻塞其他资源的正常载入。这意味着老式浏览器在开始加载页面中的样式文件、图片以及其他资源前会出现明显的延迟。
这些加载问题在较新的浏览器中有所缓解(参见我的文章《浏览器脚本加载综述》)。 从IE8、Firefox3.6、Chrome2以及Safari4开始,脚本资源大体上可以做到与其他资源并行下载。注意,我用“大体上”源于浏览器间 在处理并行加载脚本上仍然存在不统一现象。例如,在IE8和IE9 beta 上,脚本下载期间图片的正常加载会被阻塞(例子)。一个更费解的案例是,Firefox3会按顺序加载通过document.write引入的脚本,而不是并行加载。
其 次,脚本也会阻塞页面的渲染:一切在SCRIPT标签之后的DOM元素只有等到脚本加载完成才会被渲染。浏览器已经把HTML文档下载好了,假如有一个 SCRIPT在P、DIV或是UL等之前,用户却不能立即看到内容。这真悲剧。如果将SCRIPT置于HEAD标签中,则整个页面的渲染都会被阻塞。
浏览器间(特别是老式浏览器)在加载脚本上还有很多细微差别。当完全理解了影响页面加载的各种因素后,我提出了第一套有关改善脚本加载的建议:
将脚本移至底部
回到2006年、2007年期间,那时候的我刚开始如何让脚本加载更快的研究,面对脚本加载时,所有浏览器都面临同样的问题:
- 按顺序加载脚本;
- 当一个脚本正在加载时会阻塞页面上所有其他资源的加载;
- SCRIPT标签之后的内容只有等到该脚本加载完成后才会渲染。
我当时提出的解决方案是将SCRIPT标签移至页面底部。
... <script src="core.js" type="text/javascript"></script> <script src="more.js" type="text/javascript"></script> </body>
并非什么时候都可以这样做,例如,通过document.write引入的广告代码就不能顺便移动,因为必须在广告预期出现的地方执行 document.write。不过许多脚本可以比较轻松地移到页面底部。好处是显而易见的:图片加载更快、页面渲染也更快。这也是我的书《高性能网站建设指南》中14条规则中的一条。很多网站采纳了这条建议并且也看到了收益。
异步加载脚本
将脚本移至页面底部确实避免了一些问题,但其他脚本加载问题仍然存在。2008到2009年期间,浏览器仍然是按顺序加载脚本文件。这有明显的提升性能的机 会。虽然脚本(通常)需要按顺序执行,但并不需要按顺序加载。在浏览器保证按原先顺序执行的前提下脚本可以以任意顺序加载。
浏览器提供商意识到了这一点。(我自恋地认为本人在此事上做了少许贡献。)较新的浏览器(如前所述,从IE8、Firefox3.6、Chrome2以及 Safari4开始)也开始并行加载脚本了。但回到2008、2009年期间,顺序加载脚本仍然还是一个问题。一天,我在分析MSN.com时注意到即使是在Firefox2.0上脚本也能并行加载。他们使用的是动态添加SCRIPT标签的技巧:
var se = document.createElement("script");
se.src = "core.js";
document.getElementsByTagName("head")[0].appendChild(se);
最近几年我花了大部分时间来研究像这样的脚本异步加载技术。这些异步技术(总结在此文里,具体细节参看《高性能网站建设进阶指南》)汇总了老式浏览器中脚本并行加载的办法以及如何避免较新浏览器中的一些怪异特性。它们也一定程度上缓解了阻塞渲染问题:当一个脚本文件通过异步方式载入页面时,浏览器响应加快,且在加载过程中能够正常渲染页面。在这个例子中,有一个通过动态添加SCRIPT标签技术放入HEAD中的脚本,此脚本被设置为4秒下载完成。当你打开例子页面的URL你会看到页面立即渲染,这证实了脚本异步加载过程中渲染可以执行。
更多的并行下载,更快的页面渲染。我们还可以期望什么?呃⋯⋯
异步加载 + 按需执行
异步加载脚本文件提升了下载速度(更多的并行数)和渲染速度。然而当脚本下载完成后就开始解析和执行,脚本解析和执行期间浏览器停止渲染、UI被锁住。如果所有的JavaScript都需要立即执行,那这里没有一点改善空间,然而网站并没有完全使用所下载的代码(至少没有立即使用)。Alexa 美国前10的网站平均会加载229KB(压缩后)的JavaScript,而在load事件触发前这些代码的函数仅仅执行了29%。其他的71%是无用的代码、条件分支、或者很可能是些初始页面时不需要的DHTML和Ajax函数。
此发现引导我提出了将JavaScript拆分的建议:一部分用于初始加载时渲染页面的代码;另一部分是可以稍后加载的用于DHTML和Ajax的代码(参见此文或是《高性能网站建设进阶指南》的第三章)。网站通常在window的onload事件处理函数中下载页面稍后需要的代码。Gmail Mobile team 发现 当后续代码到达浏览器时UI会被锁住,这是不恰当的。总之,这些用于DHTML/Ajax的代码甚至很可能不会被用到。他们是我发现的第一批找到方法分离脚本下载期和脚本解析-执行期的家伙。他们使用的方法是对所有代码加上注释,然后当需要时再移除注释分隔符并且执行它们。GMail的技术使 用了iframe,所以需要改变你现存的代码。Stoyan已经开始探索使用image和object标签加载脚本以避免执行他们,只有到需要执行时再进行专门的解析和执行。
接下来做什么?
web 页面变得越发复杂。关注性能问题的高级开发者对页面加载需要有更强的控制力。当外部脚本被解析和执行时给予开发者控制权是明智的。目前这并非易事。很幸运,我们已经看到曙光,浏览器对LINK的REL=”PREFETCH”的支持正在增加。它提供了一个加载脚本而不解析和执行脚本的途径。浏览器提供商需要确保LINK标签有一个load事件,这样开发者才能够知道文件是否已经下载完毕。进而存放在浏览器缓存中的文件可以在真正需要时通过动态添加DOM 节点的异步方式或其他技术添加到页面中。
我们即将可以进入到脚本加载优化的下一阶段。而在万事俱备之前,先驱开发者们将如往常一样做他们应做的——继续努力,紧随最新的最佳实践。若想完全的掌控脚本加载时间点以及解析、执行的时间点,我推荐你阅读来自Gmail Mobile和Stoyan的相关文章。
鉴于过去几年的判断展情况,我打赌,脚本加载有更多的提升空间等着我们。
