管理JavaScript代码是一个棘手的问题,因为执行代码阻塞了其他处理过程,如用户界面绘制。当JavaScript引擎遇到<script>标签时,页面必须停下来等待下载和执行代码(如果是外部的),然后再继续处理页面其他部分。但是,有几种方法可以减少这种操作对性能的影响。
❑将所有<script>标签放置在页面的底部,紧靠body关闭标签</body>的上方,这样可以保证页面在脚本运行之前完成解析。
❑将脚本分组打包。页面的<script>标签越少,页面的加载速度就越快,响应也更加迅速。不论外部脚本文件,还是内联代码都应该如此。
也可以使用非阻塞方式下载JavaScript。
❑为<script>标签添加defer属性(只适用于IE和Firefox 3.5以上版本)。
❑动态创建<script>元素,并且用它下载并执行代码。
❑用XHR对象下载代码,并且将其注入到页面中。
通过使用上述策略,可以极大地提高那些大量使用JavaScript代码的网页性能。
JavaScript在浏览器中的性能,是开发者所要面对的最重要的可用性问题。此问题因JavaScript的阻塞特征而变得复杂。大多数浏览器使用单进程处理UI更新和JavaScript运行等多个任务,并且同一时间只能执行一个任务。当JavaScript运行时而其他的事情不能被浏览器处理时,JavaScript运行了多长时间,在浏览器空闲之后响应用户输入之前的等待时间就有多长。
因此,<script>标签的出现使整个页面因脚本解析和运行而出现等待。不论实际的JavaScript代码是内联的还是包含在一个不相干的外部文件中的,页面下载和解析过程必须停下,等待脚本完成这些处理才能继续。这是页面生命周期必不可少的部分,因为脚本可能在运行过程中修改页面内容。
例如,最典型的是document.write函数。
<html>
<head>
<title>Script Example</title>
</head>
<body>
<p>
<script type=/"text/javascript/">
document.write((new Date).toDateString);
</script>
</p>
</body>
</html>
当浏览器遇到一个<script>标签时,无法预知JavaScript是否在<p>标签中添加内容,因此,浏览器会停下来,运行此JavaScript代码,然后再继续解析和翻译页面。同样的事情也发生在使用src属性加载JavaScript的过程中,浏览器必须首先下载外部文件的代码,然后解析并运行此代码。这个过程会占用一些时间,使页面解析和用户交互完全阻塞。
<script>标签用于加载外部JavaScript文件,该标签可以放在HTML文档的<head>或<body>标签中,可以在其中多次出现。除此类代码外,<head>部分还包含<link>标签用于加载外部CSS文件等。因此,最好把样式和行为所依赖的部分放在一起,先加载它们,使页面可以得到正确的外观和行为,例如:
<html>
<head>
<title></title>
<script type=/"text/javascript/"src=/"file1.js/"></script>
<script type=/"text/javascript/"src=/"file2.js/"></script>
<script type=/"text/javascript/"src=/"file3.js/"></script>
<link rel=/"stylesheet/"type=/"text/css/"href=/"styles.css/">
</head>
<body>
<p></p>
</body>
</html>
虽然这些代码看起来是正确的,但是它们确实存在性能问题:在<head>部分加载了3个JavaScript文件。由于每个<script>标签阻塞了页面的解析过程,因此直到它完整地下载并运行了外部JavaScript代码之后,页面处理才能继续进行。如果脚本文本很大,用户会察觉到延迟。
浏览器在解析<body>标签之前,不会渲染页面的任何部分。用这种方法把脚本放到页面的顶端,将导致一个可以察觉的延迟:在页面打开之前,显示为一幅空白的页面,此时用户既不能阅读,也不能与页面进行交互操作。
在上面代码中,第一个JavaScript文件开始下载并阻塞了其他文件的下载过程。接下来,在file1.js下载完之后和file2.js开始下载之前有一个延时,这是file1.js完全运行所需的时间。每个文件必须等待前一个文件下载完成并运行完之后,才能开始自己的下载过程。当这些文件正在下载时,用户面对一个空白的屏幕。这是大多数浏览器的行为模式。
一般读者希望<script>标签正在下载外部资源时,不必阻塞其他<script>标签。不幸的是,JavaScript的下载仍然要阻塞其他资源(比如图片)的下载过程。即使脚本之间的下载过程互不阻塞,页面仍要等待所有JavaScript代码下载并执行完之后才能继续。因此,在浏览器允许并行下载提高性能之后,该问题并没有完全解决。脚本阻塞仍然是一个问题。
因为脚本阻塞其他页面资源的下载过程,所以推荐的办法为:将所有<script>标签放在尽可能接近<body>标签底部的位置,尽量减少对整个页面下载的影响。
<html>
<head>
<title></title>
<link rel=/"stylesheet/"type=/"text/css/"href=/"styles.css/">
</head>
<body>
<p></p>
<script type=/"text/javascript/"src=/"file1.js/"></script>
<script type=/"text/javascript/"src=/"file2.js/"></script>
<script type=/"text/javascript/"src=/"file3.js/"></script>
</body>
</html>
上面代码展示了<script>标签在HTML文件中的推荐位置。尽管脚本下载之间互相阻塞,不过页面已经下载完成并显示在用户面前了,进入页面的速度不会太慢。
由于每个<script>标签下载时会阻塞页面解析过程,因此限制页面的<script>总数也可以改善性能。这个规则对内联脚本和外部脚本同样适用。每当页面解析碰到一个<script>标签时,紧接着有一段时间用于代码执行。最小化这些延迟时间可以改善页面的整体性能。
这个问题与外部JavaScript文件处理过程略有不同。每个HTTP请求都会产生额外的性能负担,下载一个100KB的文件比下载4个25KB的文件要快。总之,减少引用外部脚本文件的数量可以降低性能损耗。在一个大型网站或网页应用需要多次请求JavaScript文件时,可以将这些文件整合成一个文件,只需要一个<script>标签引用,就可以减少性能损失。